diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/time | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
107 files changed, 22486 insertions, 0 deletions
diff --git a/third_party/rust/time-0.1.45/.cargo-checksum.json b/third_party/rust/time-0.1.45/.cargo-checksum.json new file mode 100644 index 0000000000..853f851921 --- /dev/null +++ b/third_party/rust/time-0.1.45/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"27cdb65721e671b4a942a99b69f6b8679e7ec258cd8c2e5edec8eb50a91b3b58","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb","README.md":"35b591c7481ec3ae59210fa4f9b7cecb1b16e632bfd6193b032239e74f9bfdb8","src/display.rs":"52d16abaa37b3ab577747c7d9d2ed6ded1b126458e980dc3e1a571fa6e1f9fda","src/duration.rs":"c706d392bdb7f65b23fcc20189a9a77c50b765b9c548e247238424ed6fb56a46","src/lib.rs":"86f27545d9dd5fcef7797da46ec101f22226760c1b8ac49a171d0b2fc374f478","src/parse.rs":"65bd9142d8c15eb54a8d4db6e2c48bf1adbcc875953141c17e07ba58f356a027","src/sys.rs":"f9208c822fec9dabdd9c9e70aae8822dd13bac7217e5057c82a398d24fcadc7a"},"package":"1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"}
\ No newline at end of file diff --git a/third_party/rust/time-0.1.45/Cargo.toml b/third_party/rust/time-0.1.45/Cargo.toml new file mode 100644 index 0000000000..4f6912ef9d --- /dev/null +++ b/third_party/rust/time-0.1.45/Cargo.toml @@ -0,0 +1,60 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +name = "time" +version = "0.1.45" +authors = ["The Rust Project Developers"] +exclude = [ + ".github", + "benches", +] +description = """ +Utilities for working with time-related functions in Rust. +""" +homepage = "https://github.com/time-rs/time" +documentation = "https://docs.rs/time/~0.1" +readme = "README.md" +license = "MIT/Apache-2.0" +repository = "https://github.com/time-rs/time" + +[dependencies.libc] +version = "0.2.69" + +[dependencies.rustc-serialize] +version = "0.3" +optional = true + +[dev-dependencies.log] +version = "0.4" + +[dev-dependencies.winapi] +version = "0.3.0" +features = [ + "std", + "processthreadsapi", + "winbase", +] + +[target."cfg(target_os = \"wasi\")".dependencies.wasi] +version = "=0.10.0" + +[target."cfg(windows)".dependencies.winapi] +version = "0.3.0" +features = [ + "std", + "minwinbase", + "minwindef", + "ntdef", + "profileapi", + "sysinfoapi", + "timezoneapi", +] diff --git a/third_party/rust/time-0.1.45/LICENSE-APACHE b/third_party/rust/time-0.1.45/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/third_party/rust/time-0.1.45/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/time-0.1.45/LICENSE-MIT b/third_party/rust/time-0.1.45/LICENSE-MIT new file mode 100644 index 0000000000..39d4bdb5ac --- /dev/null +++ b/third_party/rust/time-0.1.45/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/rust/time-0.1.45/README.md b/third_party/rust/time-0.1.45/README.md new file mode 100644 index 0000000000..f427eb3ea4 --- /dev/null +++ b/third_party/rust/time-0.1.45/README.md @@ -0,0 +1,31 @@ +time +==== + +Utilities for working with time-related functions in Rust + +[![build status](https://github.com/time-rs/time/workflows/Build/badge.svg?branch=v0.1)](https://github.com/time-rs/time/actions?query=branch%3Av0.1) +[![Documentation](https://docs.rs/time/badge.svg?version=0.1)](https://docs.rs/time/~0.1) +![rustc 1.21.0](https://img.shields.io/badge/rustc-1.21.0-blue) + +## time v0.1.x is Deprecated + +The 0.1.x series of this library is deprecated and in maintenance mode. No new +features will be added. Active development now occurs in the 0.2.x series. + +If you need additional functionality that this crate does not provide, check +out the [`chrono`](https://github.com/chronotope/chrono) crate. + +## Usage + +Put this in your `Cargo.toml`: + +```toml +[dependencies] +time = "0.1" +``` + +And this in your crate root: + +```rust +extern crate time; +``` diff --git a/third_party/rust/time-0.1.45/src/display.rs b/third_party/rust/time-0.1.45/src/display.rs new file mode 100644 index 0000000000..705b43078a --- /dev/null +++ b/third_party/rust/time-0.1.45/src/display.rs @@ -0,0 +1,260 @@ +use std::fmt::{self, Write}; + +use super::{TmFmt, Tm, Fmt}; + +impl<'a> fmt::Display for TmFmt<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self.format { + Fmt::Str(ref s) => { + let mut chars = s.chars(); + while let Some(ch) = chars.next() { + if ch == '%' { + // we've already validated that % always precedes + // another char + parse_type(fmt, chars.next().unwrap(), self.tm)?; + } else { + fmt.write_char(ch)?; + } + } + + Ok(()) + } + Fmt::Ctime => self.tm.to_local().asctime().fmt(fmt), + Fmt::Rfc3339 => { + if self.tm.tm_utcoff == 0 { + TmFmt { + tm: self.tm, + format: Fmt::Str("%Y-%m-%dT%H:%M:%SZ"), + }.fmt(fmt) + } else { + let s = TmFmt { + tm: self.tm, + format: Fmt::Str("%Y-%m-%dT%H:%M:%S"), + }; + let sign = if self.tm.tm_utcoff > 0 { '+' } else { '-' }; + let mut m = abs(self.tm.tm_utcoff) / 60; + let h = m / 60; + m -= h * 60; + write!(fmt, "{}{}{:02}:{:02}", s, sign, h, m) + } + } + } + } +} + +fn is_leap_year(year: i32) -> bool { + (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) +} + +fn days_in_year(year: i32) -> i32 { + if is_leap_year(year) { 366 } + else { 365 } +} + +fn iso_week_days(yday: i32, wday: i32) -> i32 { + /* The number of days from the first day of the first ISO week of this + * year to the year day YDAY with week day WDAY. + * ISO weeks start on Monday. The first ISO week has the year's first + * Thursday. + * YDAY may be as small as yday_minimum. + */ + let iso_week_start_wday: i32 = 1; /* Monday */ + let iso_week1_wday: i32 = 4; /* Thursday */ + let yday_minimum: i32 = 366; + /* Add enough to the first operand of % to make it nonnegative. */ + let big_enough_multiple_of_7: i32 = (yday_minimum / 7 + 2) * 7; + + yday - (yday - wday + iso_week1_wday + big_enough_multiple_of_7) % 7 + + iso_week1_wday - iso_week_start_wday +} + +fn iso_week(fmt: &mut fmt::Formatter, ch:char, tm: &Tm) -> fmt::Result { + let mut year = tm.tm_year + 1900; + let mut days = iso_week_days(tm.tm_yday, tm.tm_wday); + + if days < 0 { + /* This ISO week belongs to the previous year. */ + year -= 1; + days = iso_week_days(tm.tm_yday + (days_in_year(year)), tm.tm_wday); + } else { + let d = iso_week_days(tm.tm_yday - (days_in_year(year)), + tm.tm_wday); + if 0 <= d { + /* This ISO week belongs to the next year. */ + year += 1; + days = d; + } + } + + match ch { + 'G' => write!(fmt, "{}", year), + 'g' => write!(fmt, "{:02}", (year % 100 + 100) % 100), + 'V' => write!(fmt, "{:02}", days / 7 + 1), + _ => Ok(()) + } +} + +fn parse_type(fmt: &mut fmt::Formatter, ch: char, tm: &Tm) -> fmt::Result { + match ch { + 'A' => fmt.write_str(match tm.tm_wday { + 0 => "Sunday", + 1 => "Monday", + 2 => "Tuesday", + 3 => "Wednesday", + 4 => "Thursday", + 5 => "Friday", + 6 => "Saturday", + _ => unreachable!(), + }), + 'a' => fmt.write_str(match tm.tm_wday { + 0 => "Sun", + 1 => "Mon", + 2 => "Tue", + 3 => "Wed", + 4 => "Thu", + 5 => "Fri", + 6 => "Sat", + _ => unreachable!(), + }), + 'B' => fmt.write_str(match tm.tm_mon { + 0 => "January", + 1 => "February", + 2 => "March", + 3 => "April", + 4 => "May", + 5 => "June", + 6 => "July", + 7 => "August", + 8 => "September", + 9 => "October", + 10 => "November", + 11 => "December", + _ => unreachable!(), + }), + 'b' | 'h' => fmt.write_str(match tm.tm_mon { + 0 => "Jan", + 1 => "Feb", + 2 => "Mar", + 3 => "Apr", + 4 => "May", + 5 => "Jun", + 6 => "Jul", + 7 => "Aug", + 8 => "Sep", + 9 => "Oct", + 10 => "Nov", + 11 => "Dec", + _ => unreachable!(), + }), + 'C' => write!(fmt, "{:02}", (tm.tm_year + 1900) / 100), + 'c' => { + parse_type(fmt, 'a', tm)?; + fmt.write_str(" ")?; + parse_type(fmt, 'b', tm)?; + fmt.write_str(" ")?; + parse_type(fmt, 'e', tm)?; + fmt.write_str(" ")?; + parse_type(fmt, 'T', tm)?; + fmt.write_str(" ")?; + parse_type(fmt, 'Y', tm) + } + 'D' | 'x' => { + parse_type(fmt, 'm', tm)?; + fmt.write_str("/")?; + parse_type(fmt, 'd', tm)?; + fmt.write_str("/")?; + parse_type(fmt, 'y', tm) + } + 'd' => write!(fmt, "{:02}", tm.tm_mday), + 'e' => write!(fmt, "{:2}", tm.tm_mday), + 'f' => write!(fmt, "{:09}", tm.tm_nsec), + 'F' => { + parse_type(fmt, 'Y', tm)?; + fmt.write_str("-")?; + parse_type(fmt, 'm', tm)?; + fmt.write_str("-")?; + parse_type(fmt, 'd', tm) + } + 'G' => iso_week(fmt, 'G', tm), + 'g' => iso_week(fmt, 'g', tm), + 'H' => write!(fmt, "{:02}", tm.tm_hour), + 'I' => { + let mut h = tm.tm_hour; + if h == 0 { h = 12 } + if h > 12 { h -= 12 } + write!(fmt, "{:02}", h) + } + 'j' => write!(fmt, "{:03}", tm.tm_yday + 1), + 'k' => write!(fmt, "{:2}", tm.tm_hour), + 'l' => { + let mut h = tm.tm_hour; + if h == 0 { h = 12 } + if h > 12 { h -= 12 } + write!(fmt, "{:2}", h) + } + 'M' => write!(fmt, "{:02}", tm.tm_min), + 'm' => write!(fmt, "{:02}", tm.tm_mon + 1), + 'n' => fmt.write_str("\n"), + 'P' => fmt.write_str(if tm.tm_hour < 12 { "am" } else { "pm" }), + 'p' => fmt.write_str(if (tm.tm_hour) < 12 { "AM" } else { "PM" }), + 'R' => { + parse_type(fmt, 'H', tm)?; + fmt.write_str(":")?; + parse_type(fmt, 'M', tm) + } + 'r' => { + parse_type(fmt, 'I', tm)?; + fmt.write_str(":")?; + parse_type(fmt, 'M', tm)?; + fmt.write_str(":")?; + parse_type(fmt, 'S', tm)?; + fmt.write_str(" ")?; + parse_type(fmt, 'p', tm) + } + 'S' => write!(fmt, "{:02}", tm.tm_sec), + 's' => write!(fmt, "{}", tm.to_timespec().sec), + 'T' | 'X' => { + parse_type(fmt, 'H', tm)?; + fmt.write_str(":")?; + parse_type(fmt, 'M', tm)?; + fmt.write_str(":")?; + parse_type(fmt, 'S', tm) + } + 't' => fmt.write_str("\t"), + 'U' => write!(fmt, "{:02}", (tm.tm_yday - tm.tm_wday + 7) / 7), + 'u' => { + let i = tm.tm_wday; + write!(fmt, "{}", (if i == 0 { 7 } else { i })) + } + 'V' => iso_week(fmt, 'V', tm), + 'v' => { + parse_type(fmt, 'e', tm)?; + fmt.write_str("-")?; + parse_type(fmt, 'b', tm)?; + fmt.write_str("-")?; + parse_type(fmt, 'Y', tm) + } + 'W' => { + write!(fmt, "{:02}", (tm.tm_yday - (tm.tm_wday - 1 + 7) % 7 + 7) / 7) + } + 'w' => write!(fmt, "{}", tm.tm_wday), + 'Y' => write!(fmt, "{}", tm.tm_year + 1900), + 'y' => write!(fmt, "{:02}", (tm.tm_year + 1900) % 100), + // FIXME (#2350): support locale + 'Z' => fmt.write_str(if tm.tm_utcoff == 0 { "UTC"} else { "" }), + 'z' => { + let sign = if tm.tm_utcoff > 0 { '+' } else { '-' }; + let mut m = abs(tm.tm_utcoff) / 60; + let h = m / 60; + m -= h * 60; + write!(fmt, "{}{:02}{:02}", sign, h, m) + } + '+' => write!(fmt, "{}", tm.rfc3339()), + '%' => fmt.write_str("%"), + _ => unreachable!(), + } +} + +fn abs(i: i32) -> i32 { + if i < 0 {-i} else {i} +} diff --git a/third_party/rust/time-0.1.45/src/duration.rs b/third_party/rust/time-0.1.45/src/duration.rs new file mode 100644 index 0000000000..b33206c1e6 --- /dev/null +++ b/third_party/rust/time-0.1.45/src/duration.rs @@ -0,0 +1,655 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Temporal quantification + +use std::{fmt, i64}; +use std::error::Error; +use std::ops::{Add, Sub, Mul, Div, Neg, FnOnce}; +use std::time::Duration as StdDuration; + +/// The number of nanoseconds in a microsecond. +const NANOS_PER_MICRO: i32 = 1000; +/// The number of nanoseconds in a millisecond. +const NANOS_PER_MILLI: i32 = 1000_000; +/// The number of nanoseconds in seconds. +const NANOS_PER_SEC: i32 = 1_000_000_000; +/// The number of microseconds per second. +const MICROS_PER_SEC: i64 = 1000_000; +/// The number of milliseconds per second. +const MILLIS_PER_SEC: i64 = 1000; +/// The number of seconds in a minute. +const SECS_PER_MINUTE: i64 = 60; +/// The number of seconds in an hour. +const SECS_PER_HOUR: i64 = 3600; +/// The number of (non-leap) seconds in days. +const SECS_PER_DAY: i64 = 86400; +/// The number of (non-leap) seconds in a week. +const SECS_PER_WEEK: i64 = 604800; + +macro_rules! try_opt { + ($e:expr) => (match $e { Some(v) => v, None => return None }) +} + + +/// ISO 8601 time duration with nanosecond precision. +/// This also allows for the negative duration; see individual methods for details. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Duration { + secs: i64, + nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC +} + +/// The minimum possible `Duration`: `i64::MIN` milliseconds. +pub const MIN: Duration = Duration { + secs: i64::MIN / MILLIS_PER_SEC - 1, + nanos: NANOS_PER_SEC + (i64::MIN % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI +}; + +/// The maximum possible `Duration`: `i64::MAX` milliseconds. +pub const MAX: Duration = Duration { + secs: i64::MAX / MILLIS_PER_SEC, + nanos: (i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI +}; + +impl Duration { + /// Makes a new `Duration` with given number of weeks. + /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. + /// Panics when the duration is out of bounds. + #[inline] + pub fn weeks(weeks: i64) -> Duration { + let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds"); + Duration::seconds(secs) + } + + /// Makes a new `Duration` with given number of days. + /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. + /// Panics when the duration is out of bounds. + #[inline] + pub fn days(days: i64) -> Duration { + let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds"); + Duration::seconds(secs) + } + + /// Makes a new `Duration` with given number of hours. + /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// Panics when the duration is out of bounds. + #[inline] + pub fn hours(hours: i64) -> Duration { + let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours out of bounds"); + Duration::seconds(secs) + } + + /// Makes a new `Duration` with given number of minutes. + /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// Panics when the duration is out of bounds. + #[inline] + pub fn minutes(minutes: i64) -> Duration { + let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); + Duration::seconds(secs) + } + + /// Makes a new `Duration` with given number of seconds. + /// Panics when the duration is more than `i64::MAX` milliseconds + /// or less than `i64::MIN` milliseconds. + #[inline] + pub fn seconds(seconds: i64) -> Duration { + let d = Duration { secs: seconds, nanos: 0 }; + if d < MIN || d > MAX { + panic!("Duration::seconds out of bounds"); + } + d + } + + /// Makes a new `Duration` with given number of milliseconds. + #[inline] + pub fn milliseconds(milliseconds: i64) -> Duration { + let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); + let nanos = millis as i32 * NANOS_PER_MILLI; + Duration { secs: secs, nanos: nanos } + } + + /// Makes a new `Duration` with given number of microseconds. + #[inline] + pub fn microseconds(microseconds: i64) -> Duration { + let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); + let nanos = micros as i32 * NANOS_PER_MICRO; + Duration { secs: secs, nanos: nanos } + } + + /// Makes a new `Duration` with given number of nanoseconds. + #[inline] + pub fn nanoseconds(nanos: i64) -> Duration { + let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); + Duration { secs: secs, nanos: nanos as i32 } + } + + /// Runs a closure, returning the duration of time it took to run the + /// closure. + pub fn span<F>(f: F) -> Duration where F: FnOnce() { + let before = super::precise_time_ns(); + f(); + Duration::nanoseconds((super::precise_time_ns() - before) as i64) + } + + /// Returns the total number of whole weeks in the duration. + #[inline] + pub fn num_weeks(&self) -> i64 { + self.num_days() / 7 + } + + /// Returns the total number of whole days in the duration. + pub fn num_days(&self) -> i64 { + self.num_seconds() / SECS_PER_DAY + } + + /// Returns the total number of whole hours in the duration. + #[inline] + pub fn num_hours(&self) -> i64 { + self.num_seconds() / SECS_PER_HOUR + } + + /// Returns the total number of whole minutes in the duration. + #[inline] + pub fn num_minutes(&self) -> i64 { + self.num_seconds() / SECS_PER_MINUTE + } + + /// Returns the total number of whole seconds in the duration. + pub fn num_seconds(&self) -> i64 { + // If secs is negative, nanos should be subtracted from the duration. + if self.secs < 0 && self.nanos > 0 { + self.secs + 1 + } else { + self.secs + } + } + + /// Returns the number of nanoseconds such that + /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of + /// nanoseconds in the duration. + fn nanos_mod_sec(&self) -> i32 { + if self.secs < 0 && self.nanos > 0 { + self.nanos - NANOS_PER_SEC + } else { + self.nanos + } + } + + /// Returns the total number of whole milliseconds in the duration, + pub fn num_milliseconds(&self) -> i64 { + // A proper Duration will not overflow, because MIN and MAX are defined + // such that the range is exactly i64 milliseconds. + let secs_part = self.num_seconds() * MILLIS_PER_SEC; + let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI; + secs_part + nanos_part as i64 + } + + /// Returns the total number of whole microseconds in the duration, + /// or `None` on overflow (exceeding 2<sup>63</sup> microseconds in either direction). + pub fn num_microseconds(&self) -> Option<i64> { + let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); + let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO; + secs_part.checked_add(nanos_part as i64) + } + + /// Returns the total number of whole nanoseconds in the duration, + /// or `None` on overflow (exceeding 2<sup>63</sup> nanoseconds in either direction). + pub fn num_nanoseconds(&self) -> Option<i64> { + let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); + let nanos_part = self.nanos_mod_sec(); + secs_part.checked_add(nanos_part as i64) + } + + /// Add two durations, returning `None` if overflow occurred. + pub fn checked_add(&self, rhs: &Duration) -> Option<Duration> { + let mut secs = try_opt!(self.secs.checked_add(rhs.secs)); + let mut nanos = self.nanos + rhs.nanos; + if nanos >= NANOS_PER_SEC { + nanos -= NANOS_PER_SEC; + secs = try_opt!(secs.checked_add(1)); + } + let d = Duration { secs: secs, nanos: nanos }; + // Even if d is within the bounds of i64 seconds, + // it might still overflow i64 milliseconds. + if d < MIN || d > MAX { None } else { Some(d) } + } + + /// Subtract two durations, returning `None` if overflow occurred. + pub fn checked_sub(&self, rhs: &Duration) -> Option<Duration> { + let mut secs = try_opt!(self.secs.checked_sub(rhs.secs)); + let mut nanos = self.nanos - rhs.nanos; + if nanos < 0 { + nanos += NANOS_PER_SEC; + secs = try_opt!(secs.checked_sub(1)); + } + let d = Duration { secs: secs, nanos: nanos }; + // Even if d is within the bounds of i64 seconds, + // it might still overflow i64 milliseconds. + if d < MIN || d > MAX { None } else { Some(d) } + } + + /// The minimum possible `Duration`: `i64::MIN` milliseconds. + #[inline] + pub fn min_value() -> Duration { MIN } + + /// The maximum possible `Duration`: `i64::MAX` milliseconds. + #[inline] + pub fn max_value() -> Duration { MAX } + + /// A duration where the stored seconds and nanoseconds are equal to zero. + #[inline] + pub fn zero() -> Duration { + Duration { secs: 0, nanos: 0 } + } + + /// Returns `true` if the duration equals `Duration::zero()`. + #[inline] + pub fn is_zero(&self) -> bool { + self.secs == 0 && self.nanos == 0 + } + + /// Creates a `time::Duration` object from `std::time::Duration` + /// + /// This function errors when original duration is larger than the maximum + /// value supported for this type. + pub fn from_std(duration: StdDuration) -> Result<Duration, OutOfRangeError> { + // We need to check secs as u64 before coercing to i64 + if duration.as_secs() > MAX.secs as u64 { + return Err(OutOfRangeError(())); + } + let d = Duration { + secs: duration.as_secs() as i64, + nanos: duration.subsec_nanos() as i32, + }; + if d > MAX { + return Err(OutOfRangeError(())); + } + Ok(d) + } + + /// Creates a `std::time::Duration` object from `time::Duration` + /// + /// This function errors when duration is less than zero. As standard + /// library implementation is limited to non-negative values. + pub fn to_std(&self) -> Result<StdDuration, OutOfRangeError> { + if self.secs < 0 { + return Err(OutOfRangeError(())); + } + Ok(StdDuration::new(self.secs as u64, self.nanos as u32)) + } + + /// Returns the raw value of duration. + #[cfg(target_env = "sgx")] + pub(crate) fn raw(&self) -> (i64, i32) { + (self.secs, self.nanos) + } +} + +impl Neg for Duration { + type Output = Duration; + + #[inline] + fn neg(self) -> Duration { + if self.nanos == 0 { + Duration { secs: -self.secs, nanos: 0 } + } else { + Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos } + } + } +} + +impl Add for Duration { + type Output = Duration; + + fn add(self, rhs: Duration) -> Duration { + let mut secs = self.secs + rhs.secs; + let mut nanos = self.nanos + rhs.nanos; + if nanos >= NANOS_PER_SEC { + nanos -= NANOS_PER_SEC; + secs += 1; + } + Duration { secs: secs, nanos: nanos } + } +} + +impl Sub for Duration { + type Output = Duration; + + fn sub(self, rhs: Duration) -> Duration { + let mut secs = self.secs - rhs.secs; + let mut nanos = self.nanos - rhs.nanos; + if nanos < 0 { + nanos += NANOS_PER_SEC; + secs -= 1; + } + Duration { secs: secs, nanos: nanos } + } +} + +impl Mul<i32> for Duration { + type Output = Duration; + + fn mul(self, rhs: i32) -> Duration { + // Multiply nanoseconds as i64, because it cannot overflow that way. + let total_nanos = self.nanos as i64 * rhs as i64; + let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); + let secs = self.secs * rhs as i64 + extra_secs; + Duration { secs: secs, nanos: nanos as i32 } + } +} + +impl Div<i32> for Duration { + type Output = Duration; + + fn div(self, rhs: i32) -> Duration { + let mut secs = self.secs / rhs as i64; + let carry = self.secs - secs * rhs as i64; + let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64; + let mut nanos = self.nanos / rhs + extra_nanos as i32; + if nanos >= NANOS_PER_SEC { + nanos -= NANOS_PER_SEC; + secs += 1; + } + if nanos < 0 { + nanos += NANOS_PER_SEC; + secs -= 1; + } + Duration { secs: secs, nanos: nanos } + } +} + +impl fmt::Display for Duration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // technically speaking, negative duration is not valid ISO 8601, + // but we need to print it anyway. + let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") }; + + let days = abs.secs / SECS_PER_DAY; + let secs = abs.secs - days * SECS_PER_DAY; + let hasdate = days != 0; + let hastime = (secs != 0 || abs.nanos != 0) || !hasdate; + + write!(f, "{}P", sign)?; + + if hasdate { + write!(f, "{}D", days)?; + } + if hastime { + if abs.nanos == 0 { + write!(f, "T{}S", secs)?; + } else if abs.nanos % NANOS_PER_MILLI == 0 { + write!(f, "T{}.{:03}S", secs, abs.nanos / NANOS_PER_MILLI)?; + } else if abs.nanos % NANOS_PER_MICRO == 0 { + write!(f, "T{}.{:06}S", secs, abs.nanos / NANOS_PER_MICRO)?; + } else { + write!(f, "T{}.{:09}S", secs, abs.nanos)?; + } + } + Ok(()) + } +} + +/// Represents error when converting `Duration` to/from a standard library +/// implementation +/// +/// The `std::time::Duration` supports a range from zero to `u64::MAX` +/// *seconds*, while this module supports signed range of up to +/// `i64::MAX` of *milliseconds*. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OutOfRangeError(()); + +impl fmt::Display for OutOfRangeError { + #[allow(deprecated)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl Error for OutOfRangeError { + fn description(&self) -> &str { + "Source duration value is out of range for the target type" + } +} + +// Copied from libnum +#[inline] +fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { + (div_floor_64(this, other), mod_floor_64(this, other)) +} + +#[inline] +fn div_floor_64(this: i64, other: i64) -> i64 { + match div_rem_64(this, other) { + (d, r) if (r > 0 && other < 0) + || (r < 0 && other > 0) => d - 1, + (d, _) => d, + } +} + +#[inline] +fn mod_floor_64(this: i64, other: i64) -> i64 { + match this % other { + r if (r > 0 && other < 0) + || (r < 0 && other > 0) => r + other, + r => r, + } +} + +#[inline] +fn div_rem_64(this: i64, other: i64) -> (i64, i64) { + (this / other, this % other) +} + +#[cfg(test)] +mod tests { + use super::{Duration, MIN, MAX, OutOfRangeError}; + use std::{i32, i64}; + use std::time::Duration as StdDuration; + + #[test] + fn test_duration() { + assert!(Duration::seconds(1) != Duration::zero()); + assert_eq!(Duration::seconds(1) + Duration::seconds(2), Duration::seconds(3)); + assert_eq!(Duration::seconds(86399) + Duration::seconds(4), + Duration::days(1) + Duration::seconds(3)); + assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863000)); + assert_eq!(Duration::days(10) - Duration::seconds(1000000), Duration::seconds(-136000)); + assert_eq!(Duration::days(2) + Duration::seconds(86399) + + Duration::nanoseconds(1234567890), + Duration::days(3) + Duration::nanoseconds(234567890)); + assert_eq!(-Duration::days(3), Duration::days(-3)); + assert_eq!(-(Duration::days(3) + Duration::seconds(70)), + Duration::days(-4) + Duration::seconds(86400-70)); + } + + #[test] + fn test_duration_num_days() { + assert_eq!(Duration::zero().num_days(), 0); + assert_eq!(Duration::days(1).num_days(), 1); + assert_eq!(Duration::days(-1).num_days(), -1); + assert_eq!(Duration::seconds(86399).num_days(), 0); + assert_eq!(Duration::seconds(86401).num_days(), 1); + assert_eq!(Duration::seconds(-86399).num_days(), 0); + assert_eq!(Duration::seconds(-86401).num_days(), -1); + assert_eq!(Duration::days(i32::MAX as i64).num_days(), i32::MAX as i64); + assert_eq!(Duration::days(i32::MIN as i64).num_days(), i32::MIN as i64); + } + + #[test] + fn test_duration_num_seconds() { + assert_eq!(Duration::zero().num_seconds(), 0); + assert_eq!(Duration::seconds(1).num_seconds(), 1); + assert_eq!(Duration::seconds(-1).num_seconds(), -1); + assert_eq!(Duration::milliseconds(999).num_seconds(), 0); + assert_eq!(Duration::milliseconds(1001).num_seconds(), 1); + assert_eq!(Duration::milliseconds(-999).num_seconds(), 0); + assert_eq!(Duration::milliseconds(-1001).num_seconds(), -1); + } + + #[test] + fn test_duration_num_milliseconds() { + assert_eq!(Duration::zero().num_milliseconds(), 0); + assert_eq!(Duration::milliseconds(1).num_milliseconds(), 1); + assert_eq!(Duration::milliseconds(-1).num_milliseconds(), -1); + assert_eq!(Duration::microseconds(999).num_milliseconds(), 0); + assert_eq!(Duration::microseconds(1001).num_milliseconds(), 1); + assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0); + assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1); + assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), i64::MAX); + assert_eq!(Duration::milliseconds(i64::MIN).num_milliseconds(), i64::MIN); + assert_eq!(MAX.num_milliseconds(), i64::MAX); + assert_eq!(MIN.num_milliseconds(), i64::MIN); + } + + #[test] + fn test_duration_num_microseconds() { + assert_eq!(Duration::zero().num_microseconds(), Some(0)); + assert_eq!(Duration::microseconds(1).num_microseconds(), Some(1)); + assert_eq!(Duration::microseconds(-1).num_microseconds(), Some(-1)); + assert_eq!(Duration::nanoseconds(999).num_microseconds(), Some(0)); + assert_eq!(Duration::nanoseconds(1001).num_microseconds(), Some(1)); + assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0)); + assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1)); + assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX)); + assert_eq!(Duration::microseconds(i64::MIN).num_microseconds(), Some(i64::MIN)); + assert_eq!(MAX.num_microseconds(), None); + assert_eq!(MIN.num_microseconds(), None); + + // overflow checks + const MICROS_PER_DAY: i64 = 86400_000_000; + assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY).num_microseconds(), + Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY)); + assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY).num_microseconds(), + Some(i64::MIN / MICROS_PER_DAY * MICROS_PER_DAY)); + assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); + assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY - 1).num_microseconds(), None); + } + + #[test] + fn test_duration_num_nanoseconds() { + assert_eq!(Duration::zero().num_nanoseconds(), Some(0)); + assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1)); + assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1)); + assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX)); + assert_eq!(Duration::nanoseconds(i64::MIN).num_nanoseconds(), Some(i64::MIN)); + assert_eq!(MAX.num_nanoseconds(), None); + assert_eq!(MIN.num_nanoseconds(), None); + + // overflow checks + const NANOS_PER_DAY: i64 = 86400_000_000_000; + assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(), + Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY)); + assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY).num_nanoseconds(), + Some(i64::MIN / NANOS_PER_DAY * NANOS_PER_DAY)); + assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); + assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY - 1).num_nanoseconds(), None); + } + + #[test] + fn test_duration_checked_ops() { + assert_eq!(Duration::milliseconds(i64::MAX - 1).checked_add(&Duration::microseconds(999)), + Some(Duration::milliseconds(i64::MAX - 2) + Duration::microseconds(1999))); + assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::microseconds(1000)) + .is_none()); + + assert_eq!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(0)), + Some(Duration::milliseconds(i64::MIN))); + assert!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(1)) + .is_none()); + } + + #[test] + fn test_duration_mul() { + assert_eq!(Duration::zero() * i32::MAX, Duration::zero()); + assert_eq!(Duration::zero() * i32::MIN, Duration::zero()); + assert_eq!(Duration::nanoseconds(1) * 0, Duration::zero()); + assert_eq!(Duration::nanoseconds(1) * 1, Duration::nanoseconds(1)); + assert_eq!(Duration::nanoseconds(1) * 1_000_000_000, Duration::seconds(1)); + assert_eq!(Duration::nanoseconds(1) * -1_000_000_000, -Duration::seconds(1)); + assert_eq!(-Duration::nanoseconds(1) * 1_000_000_000, -Duration::seconds(1)); + assert_eq!(Duration::nanoseconds(30) * 333_333_333, + Duration::seconds(10) - Duration::nanoseconds(10)); + assert_eq!((Duration::nanoseconds(1) + Duration::seconds(1) + Duration::days(1)) * 3, + Duration::nanoseconds(3) + Duration::seconds(3) + Duration::days(3)); + assert_eq!(Duration::milliseconds(1500) * -2, Duration::seconds(-3)); + assert_eq!(Duration::milliseconds(-1500) * 2, Duration::seconds(-3)); + } + + #[test] + fn test_duration_div() { + assert_eq!(Duration::zero() / i32::MAX, Duration::zero()); + assert_eq!(Duration::zero() / i32::MIN, Duration::zero()); + assert_eq!(Duration::nanoseconds(123_456_789) / 1, Duration::nanoseconds(123_456_789)); + assert_eq!(Duration::nanoseconds(123_456_789) / -1, -Duration::nanoseconds(123_456_789)); + assert_eq!(-Duration::nanoseconds(123_456_789) / -1, Duration::nanoseconds(123_456_789)); + assert_eq!(-Duration::nanoseconds(123_456_789) / 1, -Duration::nanoseconds(123_456_789)); + assert_eq!(Duration::seconds(1) / 3, Duration::nanoseconds(333_333_333)); + assert_eq!(Duration::seconds(4) / 3, Duration::nanoseconds(1_333_333_333)); + assert_eq!(Duration::seconds(-1) / 2, Duration::milliseconds(-500)); + assert_eq!(Duration::seconds(1) / -2, Duration::milliseconds(-500)); + assert_eq!(Duration::seconds(-1) / -2, Duration::milliseconds(500)); + assert_eq!(Duration::seconds(-4) / 3, Duration::nanoseconds(-1_333_333_333)); + assert_eq!(Duration::seconds(-4) / -3, Duration::nanoseconds(1_333_333_333)); + } + + #[test] + fn test_duration_fmt() { + assert_eq!(Duration::zero().to_string(), "PT0S"); + assert_eq!(Duration::days(42).to_string(), "P42D"); + assert_eq!(Duration::days(-42).to_string(), "-P42D"); + assert_eq!(Duration::seconds(42).to_string(), "PT42S"); + assert_eq!(Duration::milliseconds(42).to_string(), "PT0.042S"); + assert_eq!(Duration::microseconds(42).to_string(), "PT0.000042S"); + assert_eq!(Duration::nanoseconds(42).to_string(), "PT0.000000042S"); + assert_eq!((Duration::days(7) + Duration::milliseconds(6543)).to_string(), + "P7DT6.543S"); + assert_eq!(Duration::seconds(-86401).to_string(), "-P1DT1S"); + assert_eq!(Duration::nanoseconds(-1).to_string(), "-PT0.000000001S"); + + // the format specifier should have no effect on `Duration` + assert_eq!(format!("{:30}", Duration::days(1) + Duration::milliseconds(2345)), + "P1DT2.345S"); + } + + #[test] + fn test_to_std() { + assert_eq!(Duration::seconds(1).to_std(), Ok(StdDuration::new(1, 0))); + assert_eq!(Duration::seconds(86401).to_std(), Ok(StdDuration::new(86401, 0))); + assert_eq!(Duration::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123000000))); + assert_eq!(Duration::milliseconds(123765).to_std(), Ok(StdDuration::new(123, 765000000))); + assert_eq!(Duration::nanoseconds(777).to_std(), Ok(StdDuration::new(0, 777))); + assert_eq!(MAX.to_std(), Ok(StdDuration::new(9223372036854775, 807000000))); + assert_eq!(Duration::seconds(-1).to_std(), + Err(OutOfRangeError(()))); + assert_eq!(Duration::milliseconds(-1).to_std(), + Err(OutOfRangeError(()))); + } + + #[test] + fn test_from_std() { + assert_eq!(Ok(Duration::seconds(1)), + Duration::from_std(StdDuration::new(1, 0))); + assert_eq!(Ok(Duration::seconds(86401)), + Duration::from_std(StdDuration::new(86401, 0))); + assert_eq!(Ok(Duration::milliseconds(123)), + Duration::from_std(StdDuration::new(0, 123000000))); + assert_eq!(Ok(Duration::milliseconds(123765)), + Duration::from_std(StdDuration::new(123, 765000000))); + assert_eq!(Ok(Duration::nanoseconds(777)), + Duration::from_std(StdDuration::new(0, 777))); + assert_eq!(Ok(MAX), + Duration::from_std(StdDuration::new(9223372036854775, 807000000))); + assert_eq!(Duration::from_std(StdDuration::new(9223372036854776, 0)), + Err(OutOfRangeError(()))); + assert_eq!(Duration::from_std(StdDuration::new(9223372036854775, 807000001)), + Err(OutOfRangeError(()))); + } +} diff --git a/third_party/rust/time-0.1.45/src/lib.rs b/third_party/rust/time-0.1.45/src/lib.rs new file mode 100644 index 0000000000..c5a937427f --- /dev/null +++ b/third_party/rust/time-0.1.45/src/lib.rs @@ -0,0 +1,1283 @@ +// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Simple time handling. +//! +//! # Usage +//! +//! This crate is [on crates.io](https://crates.io/crates/time) and can be +//! used by adding `time` to the dependencies in your project's `Cargo.toml`. +//! +//! ```toml +//! [dependencies] +//! time = "0.1" +//! ``` +//! +//! And this in your crate root: +//! +//! ```rust +//! extern crate time; +//! ``` +//! +//! This crate uses the same syntax for format strings as the +//! [`strftime()`](http://man7.org/linux/man-pages/man3/strftime.3.html) +//! function from the C standard library. + +#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://www.rust-lang.org/favicon.ico", + html_root_url = "https://doc.rust-lang.org/time/")] +#![allow(unknown_lints)] +#![allow(ellipsis_inclusive_range_patterns)] // `..=` requires Rust 1.26 +#![allow(trivial_numeric_casts)] + +#[cfg(unix)] extern crate libc; +#[cfg(windows)] extern crate winapi; +#[cfg(feature = "rustc-serialize")] extern crate rustc_serialize; + +#[cfg(target_os = "wasi")] extern crate wasi; + +#[cfg(test)] #[macro_use] extern crate log; + +use std::cmp::Ordering; +use std::error::Error; +use std::fmt; +use std::ops::{Add, Sub}; + +pub use duration::{Duration, OutOfRangeError}; + +use self::ParseError::{InvalidDay, InvalidDayOfMonth, InvalidDayOfWeek, + InvalidDayOfYear, InvalidFormatSpecifier, InvalidHour, + InvalidMinute, InvalidMonth, InvalidSecond, InvalidTime, + InvalidYear, InvalidZoneOffset, InvalidSecondsSinceEpoch, + MissingFormatConverter, UnexpectedCharacter}; + +pub use parse::strptime; + +mod display; +mod duration; +mod parse; +mod sys; + +static NSEC_PER_SEC: i32 = 1_000_000_000; + +/// A record specifying a time value in seconds and nanoseconds, where +/// nanoseconds represent the offset from the given second. +/// +/// For example a timespec of 1.2 seconds after the beginning of the epoch would +/// be represented as {sec: 1, nsec: 200000000}. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +pub struct Timespec { pub sec: i64, pub nsec: i32 } +/* + * Timespec assumes that pre-epoch Timespecs have negative sec and positive + * nsec fields. Darwin's and Linux's struct timespec functions handle pre- + * epoch timestamps using a "two steps back, one step forward" representation, + * though the man pages do not actually document this. For example, the time + * -1.2 seconds before the epoch is represented by `Timespec { sec: -2_i64, + * nsec: 800_000_000 }`. + */ +impl Timespec { + pub fn new(sec: i64, nsec: i32) -> Timespec { + assert!(nsec >= 0 && nsec < NSEC_PER_SEC); + Timespec { sec: sec, nsec: nsec } + } +} + +impl Add<Duration> for Timespec { + type Output = Timespec; + + fn add(self, other: Duration) -> Timespec { + let d_sec = other.num_seconds(); + // It is safe to unwrap the nanoseconds, because there cannot be + // more than one second left, which fits in i64 and in i32. + let d_nsec = (other - Duration::seconds(d_sec)) + .num_nanoseconds().unwrap() as i32; + let mut sec = self.sec + d_sec; + let mut nsec = self.nsec + d_nsec; + if nsec >= NSEC_PER_SEC { + nsec -= NSEC_PER_SEC; + sec += 1; + } else if nsec < 0 { + nsec += NSEC_PER_SEC; + sec -= 1; + } + Timespec::new(sec, nsec) + } +} + +impl Sub<Duration> for Timespec { + type Output = Timespec; + + fn sub(self, other: Duration) -> Timespec { + let d_sec = other.num_seconds(); + // It is safe to unwrap the nanoseconds, because there cannot be + // more than one second left, which fits in i64 and in i32. + let d_nsec = (other - Duration::seconds(d_sec)) + .num_nanoseconds().unwrap() as i32; + let mut sec = self.sec - d_sec; + let mut nsec = self.nsec - d_nsec; + if nsec >= NSEC_PER_SEC { + nsec -= NSEC_PER_SEC; + sec += 1; + } else if nsec < 0 { + nsec += NSEC_PER_SEC; + sec -= 1; + } + Timespec::new(sec, nsec) + } +} + +impl Sub<Timespec> for Timespec { + type Output = Duration; + + fn sub(self, other: Timespec) -> Duration { + let sec = self.sec - other.sec; + let nsec = self.nsec - other.nsec; + Duration::seconds(sec) + Duration::nanoseconds(nsec as i64) + } +} + +/** + * Returns the current time as a `timespec` containing the seconds and + * nanoseconds since 1970-01-01T00:00:00Z. + */ +pub fn get_time() -> Timespec { + let (sec, nsec) = sys::get_time(); + Timespec::new(sec, nsec) +} + + +/** + * Returns the current value of a high-resolution performance counter + * in nanoseconds since an unspecified epoch. + */ +#[inline] +pub fn precise_time_ns() -> u64 { + sys::get_precise_ns() +} + + +/** + * Returns the current value of a high-resolution performance counter + * in seconds since an unspecified epoch. + */ +pub fn precise_time_s() -> f64 { + return (precise_time_ns() as f64) / 1000000000.; +} + +/// An opaque structure representing a moment in time. +/// +/// The only operation that can be performed on a `PreciseTime` is the +/// calculation of the `Duration` of time that lies between them. +/// +/// # Examples +/// +/// Repeatedly call a function for 1 second: +/// +/// ```rust +/// use time::{Duration, PreciseTime}; +/// # fn do_some_work() {} +/// +/// let start = PreciseTime::now(); +/// +/// while start.to(PreciseTime::now()) < Duration::seconds(1) { +/// do_some_work(); +/// } +/// ``` +#[derive(Copy, Clone)] +pub struct PreciseTime(u64); + +impl PreciseTime { + /// Returns a `PreciseTime` representing the current moment in time. + pub fn now() -> PreciseTime { + PreciseTime(precise_time_ns()) + } + + /// Returns a `Duration` representing the span of time from the value of + /// `self` to the value of `later`. + /// + /// # Notes + /// + /// If `later` represents a time before `self`, the result of this method + /// is unspecified. + /// + /// If `later` represents a time more than 293 years after `self`, the + /// result of this method is unspecified. + #[inline] + pub fn to(&self, later: PreciseTime) -> Duration { + // NB: even if later is less than self due to overflow, this will work + // since the subtraction will underflow properly as well. + // + // We could deal with the overflow when casting to an i64, but all that + // gets us is the ability to handle intervals of up to 584 years, which + // seems not very useful :) + Duration::nanoseconds((later.0 - self.0) as i64) + } +} + +/// A structure representing a moment in time. +/// +/// `SteadyTime`s are generated by a "steady" clock, that is, a clock which +/// never experiences discontinuous jumps and for which time always flows at +/// the same rate. +/// +/// # Examples +/// +/// Repeatedly call a function for 1 second: +/// +/// ```rust +/// # use time::{Duration, SteadyTime}; +/// # fn do_some_work() {} +/// let start = SteadyTime::now(); +/// +/// while SteadyTime::now() - start < Duration::seconds(1) { +/// do_some_work(); +/// } +/// ``` +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)] +pub struct SteadyTime(sys::SteadyTime); + +impl SteadyTime { + /// Returns a `SteadyTime` representing the current moment in time. + pub fn now() -> SteadyTime { + SteadyTime(sys::SteadyTime::now()) + } +} + +impl fmt::Display for SteadyTime { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + // TODO: needs a display customization + fmt::Debug::fmt(self, fmt) + } +} + +impl Sub for SteadyTime { + type Output = Duration; + + fn sub(self, other: SteadyTime) -> Duration { + self.0 - other.0 + } +} + +impl Sub<Duration> for SteadyTime { + type Output = SteadyTime; + + fn sub(self, other: Duration) -> SteadyTime { + SteadyTime(self.0 - other) + } +} + +impl Add<Duration> for SteadyTime { + type Output = SteadyTime; + + fn add(self, other: Duration) -> SteadyTime { + SteadyTime(self.0 + other) + } +} + +#[cfg(not(any(windows, target_env = "sgx")))] +pub fn tzset() { + extern { fn tzset(); } + unsafe { tzset() } +} + + +#[cfg(any(windows, target_env = "sgx"))] +pub fn tzset() {} + +/// Holds a calendar date and time broken down into its components (year, month, +/// day, and so on), also called a broken-down time value. +// FIXME: use c_int instead of i32? +#[repr(C)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +pub struct Tm { + /// Seconds after the minute - [0, 60] + pub tm_sec: i32, + + /// Minutes after the hour - [0, 59] + pub tm_min: i32, + + /// Hours after midnight - [0, 23] + pub tm_hour: i32, + + /// Day of the month - [1, 31] + pub tm_mday: i32, + + /// Months since January - [0, 11] + pub tm_mon: i32, + + /// Years since 1900 + pub tm_year: i32, + + /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. + pub tm_wday: i32, + + /// Days since January 1 - [0, 365] + pub tm_yday: i32, + + /// Daylight Saving Time flag. + /// + /// This value is positive if Daylight Saving Time is in effect, zero if + /// Daylight Saving Time is not in effect, and negative if this information + /// is not available. + pub tm_isdst: i32, + + /// Identifies the time zone that was used to compute this broken-down time + /// value, including any adjustment for Daylight Saving Time. This is the + /// number of seconds east of UTC. For example, for U.S. Pacific Daylight + /// Time, the value is `-7*60*60 = -25200`. + pub tm_utcoff: i32, + + /// Nanoseconds after the second - [0, 10<sup>9</sup> - 1] + pub tm_nsec: i32, +} + +impl Add<Duration> for Tm { + type Output = Tm; + + /// The resulting Tm is in UTC. + // FIXME: The resulting Tm should have the same timezone as `self`; + // however, we need a function such as `at_tm(clock: Timespec, offset: i32)` + // for this. + fn add(self, other: Duration) -> Tm { + at_utc(self.to_timespec() + other) + } +} + +impl Sub<Duration> for Tm { + type Output = Tm; + + /// The resulting Tm is in UTC. + // FIXME: The resulting Tm should have the same timezone as `self`; + // however, we need a function such as `at_tm(clock: Timespec, offset: i32)` + // for this. + fn sub(self, other: Duration) -> Tm { + at_utc(self.to_timespec() - other) + } +} + +impl Sub<Tm> for Tm { + type Output = Duration; + + fn sub(self, other: Tm) -> Duration { + self.to_timespec() - other.to_timespec() + } +} + +impl PartialOrd for Tm { + fn partial_cmp(&self, other: &Tm) -> Option<Ordering> { + self.to_timespec().partial_cmp(&other.to_timespec()) + } +} + +impl Ord for Tm { + fn cmp(&self, other: &Tm) -> Ordering { + self.to_timespec().cmp(&other.to_timespec()) + } +} + +pub fn empty_tm() -> Tm { + Tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_utcoff: 0, + tm_nsec: 0, + } +} + +/// Returns the specified time in UTC +pub fn at_utc(clock: Timespec) -> Tm { + let Timespec { sec, nsec } = clock; + let mut tm = empty_tm(); + sys::time_to_utc_tm(sec, &mut tm); + tm.tm_nsec = nsec; + tm +} + +/// Returns the current time in UTC +pub fn now_utc() -> Tm { + at_utc(get_time()) +} + +/// Returns the specified time in the local timezone +pub fn at(clock: Timespec) -> Tm { + let Timespec { sec, nsec } = clock; + let mut tm = empty_tm(); + sys::time_to_local_tm(sec, &mut tm); + tm.tm_nsec = nsec; + tm +} + +/// Returns the current time in the local timezone +pub fn now() -> Tm { + at(get_time()) +} + +impl Tm { + /// Convert time to the seconds from January 1, 1970 + pub fn to_timespec(&self) -> Timespec { + let sec = match self.tm_utcoff { + 0 => sys::utc_tm_to_time(self), + _ => sys::local_tm_to_time(self) + }; + + Timespec::new(sec, self.tm_nsec) + } + + /// Convert time to the local timezone + pub fn to_local(&self) -> Tm { + at(self.to_timespec()) + } + + /// Convert time to the UTC + pub fn to_utc(&self) -> Tm { + match self.tm_utcoff { + 0 => *self, + _ => at_utc(self.to_timespec()) + } + } + + /** + * Returns a TmFmt that outputs according to the `asctime` format in ISO + * C, in the local timezone. + * + * Example: "Thu Jan 1 00:00:00 1970" + */ + pub fn ctime(&self) -> TmFmt { + TmFmt { + tm: self, + format: Fmt::Ctime, + } + } + + /** + * Returns a TmFmt that outputs according to the `asctime` format in ISO + * C. + * + * Example: "Thu Jan 1 00:00:00 1970" + */ + pub fn asctime(&self) -> TmFmt { + TmFmt { + tm: self, + format: Fmt::Str("%c"), + } + } + + /// Formats the time according to the format string. + pub fn strftime<'a>(&'a self, format: &'a str) -> Result<TmFmt<'a>, ParseError> { + validate_format(TmFmt { + tm: self, + format: Fmt::Str(format), + }) + } + + /** + * Returns a TmFmt that outputs according to RFC 822. + * + * local: "Thu, 22 Mar 2012 07:53:18 PST" + * utc: "Thu, 22 Mar 2012 14:53:18 GMT" + */ + pub fn rfc822(&self) -> TmFmt { + let fmt = if self.tm_utcoff == 0 { + "%a, %d %b %Y %T GMT" + } else { + "%a, %d %b %Y %T %Z" + }; + TmFmt { + tm: self, + format: Fmt::Str(fmt), + } + } + + /** + * Returns a TmFmt that outputs according to RFC 822 with Zulu time. + * + * local: "Thu, 22 Mar 2012 07:53:18 -0700" + * utc: "Thu, 22 Mar 2012 14:53:18 -0000" + */ + pub fn rfc822z(&self) -> TmFmt { + TmFmt { + tm: self, + format: Fmt::Str("%a, %d %b %Y %T %z"), + } + } + + /** + * Returns a TmFmt that outputs according to RFC 3339. RFC 3339 is + * compatible with ISO 8601. + * + * local: "2012-02-22T07:53:18-07:00" + * utc: "2012-02-22T14:53:18Z" + */ + pub fn rfc3339<'a>(&'a self) -> TmFmt { + TmFmt { + tm: self, + format: Fmt::Rfc3339, + } + } +} + +#[derive(Copy, PartialEq, Debug, Clone)] +pub enum ParseError { + InvalidSecond, + InvalidMinute, + InvalidHour, + InvalidDay, + InvalidMonth, + InvalidYear, + InvalidDayOfWeek, + InvalidDayOfMonth, + InvalidDayOfYear, + InvalidZoneOffset, + InvalidTime, + InvalidSecondsSinceEpoch, + MissingFormatConverter, + InvalidFormatSpecifier(char), + UnexpectedCharacter(char, char), +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[allow(deprecated)] + match *self { + InvalidFormatSpecifier(ch) => { + write!(f, "{}: %{}", self.description(), ch) + } + UnexpectedCharacter(a, b) => { + write!(f, "expected: `{}`, found: `{}`", a, b) + } + _ => write!(f, "{}", self.description()) + } + } +} + +impl Error for ParseError { + fn description(&self) -> &str { + match *self { + InvalidSecond => "Invalid second.", + InvalidMinute => "Invalid minute.", + InvalidHour => "Invalid hour.", + InvalidDay => "Invalid day.", + InvalidMonth => "Invalid month.", + InvalidYear => "Invalid year.", + InvalidDayOfWeek => "Invalid day of the week.", + InvalidDayOfMonth => "Invalid day of the month.", + InvalidDayOfYear => "Invalid day of the year.", + InvalidZoneOffset => "Invalid zone offset.", + InvalidTime => "Invalid time.", + InvalidSecondsSinceEpoch => "Invalid seconds since epoch.", + MissingFormatConverter => "missing format converter after `%`", + InvalidFormatSpecifier(..) => "invalid format specifier", + UnexpectedCharacter(..) => "Unexpected character.", + } + } +} + +/// A wrapper around a `Tm` and format string that implements Display. +#[derive(Debug)] +pub struct TmFmt<'a> { + tm: &'a Tm, + format: Fmt<'a> +} + +#[derive(Debug)] +enum Fmt<'a> { + Str(&'a str), + Rfc3339, + Ctime, +} + +fn validate_format<'a>(fmt: TmFmt<'a>) -> Result<TmFmt<'a>, ParseError> { + + match (fmt.tm.tm_wday, fmt.tm.tm_mon) { + (0...6, 0...11) => (), + (_wday, 0...11) => return Err(InvalidDayOfWeek), + (0...6, _mon) => return Err(InvalidMonth), + _ => return Err(InvalidDay) + } + match fmt.format { + Fmt::Str(ref s) => { + let mut chars = s.chars(); + loop { + match chars.next() { + Some('%') => { + match chars.next() { + Some('A') | Some('a') | Some('B') | Some('b') | + Some('C') | Some('c') | Some('D') | Some('d') | + Some('e') | Some('F') | Some('f') | Some('G') | + Some('g') | Some('H') | Some('h') | Some('I') | + Some('j') | Some('k') | Some('l') | Some('M') | + Some('m') | Some('n') | Some('P') | Some('p') | + Some('R') | Some('r') | Some('S') | Some('s') | + Some('T') | Some('t') | Some('U') | Some('u') | + Some('V') | Some('v') | Some('W') | Some('w') | + Some('X') | Some('x') | Some('Y') | Some('y') | + Some('Z') | Some('z') | Some('+') | Some('%') => (), + + Some(c) => return Err(InvalidFormatSpecifier(c)), + None => return Err(MissingFormatConverter), + } + }, + None => break, + _ => () + } + } + }, + _ => () + } + Ok(fmt) +} + +/// Formats the time according to the format string. +pub fn strftime(format: &str, tm: &Tm) -> Result<String, ParseError> { + tm.strftime(format).map(|fmt| fmt.to_string()) +} + +#[cfg(test)] +mod tests { + use super::{Timespec, get_time, precise_time_ns, precise_time_s, + at_utc, at, strptime, PreciseTime, SteadyTime, ParseError, Duration}; + use super::ParseError::{InvalidTime, InvalidYear, MissingFormatConverter, + InvalidFormatSpecifier}; + + #[allow(deprecated)] // `Once::new` is const starting in Rust 1.32 + use std::sync::ONCE_INIT; + use std::sync::{Once, Mutex, MutexGuard, LockResult}; + use std::i32; + use std::mem; + + struct TzReset { + _tzreset: ::sys::TzReset, + _lock: LockResult<MutexGuard<'static, ()>>, + } + + fn set_time_zone_la_or_london(london: bool) -> TzReset { + // Lock manages current timezone because some tests require LA some + // London + static mut LOCK: *mut Mutex<()> = 0 as *mut _; + #[allow(deprecated)] // `Once::new` is const starting in Rust 1.32 + static INIT: Once = ONCE_INIT; + + unsafe { + INIT.call_once(|| { + LOCK = mem::transmute(Box::new(Mutex::new(()))); + }); + + let timezone_lock = (*LOCK).lock(); + let reset_func = if london { + ::sys::set_london_with_dst_time_zone() + } else { + ::sys::set_los_angeles_time_zone() + }; + TzReset { + _lock: timezone_lock, + _tzreset: reset_func, + } + } + } + + fn set_time_zone() -> TzReset { + set_time_zone_la_or_london(false) + } + + fn set_time_zone_london_dst() -> TzReset { + set_time_zone_la_or_london(true) + } + + #[test] + fn test_get_time() { + static SOME_RECENT_DATE: i64 = 1577836800i64; // 2020-01-01T00:00:00Z + static SOME_FUTURE_DATE: i64 = i32::MAX as i64; // Y2038 + + let tv1 = get_time(); + debug!("tv1={} sec + {} nsec", tv1.sec, tv1.nsec); + + assert!(tv1.sec > SOME_RECENT_DATE); + assert!(tv1.nsec < 1000000000i32); + + let tv2 = get_time(); + debug!("tv2={} sec + {} nsec", tv2.sec, tv2.nsec); + + assert!(tv2.sec >= tv1.sec); + assert!(tv2.sec < SOME_FUTURE_DATE); + assert!(tv2.nsec < 1000000000i32); + if tv2.sec == tv1.sec { + assert!(tv2.nsec >= tv1.nsec); + } + } + + #[test] + fn test_precise_time() { + let s0 = precise_time_s(); + debug!("s0={} sec", s0); + assert!(s0 > 0.); + + let ns0 = precise_time_ns(); + let ns1 = precise_time_ns(); + debug!("ns0={} ns", ns0); + debug!("ns1={} ns", ns1); + assert!(ns1 >= ns0); + + let ns2 = precise_time_ns(); + debug!("ns2={} ns", ns2); + assert!(ns2 >= ns1); + } + + #[test] + fn test_precise_time_to() { + let t0 = PreciseTime(1000); + let t1 = PreciseTime(1023); + assert_eq!(Duration::nanoseconds(23), t0.to(t1)); + } + + #[test] + fn test_at_utc() { + let _reset = set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + + assert_eq!(utc.tm_sec, 30); + assert_eq!(utc.tm_min, 31); + assert_eq!(utc.tm_hour, 23); + assert_eq!(utc.tm_mday, 13); + assert_eq!(utc.tm_mon, 1); + assert_eq!(utc.tm_year, 109); + assert_eq!(utc.tm_wday, 5); + assert_eq!(utc.tm_yday, 43); + assert_eq!(utc.tm_isdst, 0); + assert_eq!(utc.tm_utcoff, 0); + assert_eq!(utc.tm_nsec, 54321); + } + + #[test] + fn test_at() { + let _reset = set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let local = at(time); + + debug!("time_at: {:?}", local); + + assert_eq!(local.tm_sec, 30); + assert_eq!(local.tm_min, 31); + assert_eq!(local.tm_hour, 15); + assert_eq!(local.tm_mday, 13); + assert_eq!(local.tm_mon, 1); + assert_eq!(local.tm_year, 109); + assert_eq!(local.tm_wday, 5); + assert_eq!(local.tm_yday, 43); + assert_eq!(local.tm_isdst, 0); + assert_eq!(local.tm_utcoff, -28800); + assert_eq!(local.tm_nsec, 54321); + } + + #[test] + fn test_to_timespec() { + let _reset = set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + + assert_eq!(utc.to_timespec(), time); + assert_eq!(utc.to_local().to_timespec(), time); + } + + #[test] + fn test_conversions() { + let _reset = set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + let local = at(time); + + assert!(local.to_local() == local); + assert!(local.to_utc() == utc); + assert!(local.to_utc().to_local() == local); + assert!(utc.to_utc() == utc); + assert!(utc.to_local() == local); + assert!(utc.to_local().to_utc() == utc); + } + + #[test] + fn test_strptime() { + let _reset = set_time_zone(); + + match strptime("", "") { + Ok(ref tm) => { + assert!(tm.tm_sec == 0); + assert!(tm.tm_min == 0); + assert!(tm.tm_hour == 0); + assert!(tm.tm_mday == 0); + assert!(tm.tm_mon == 0); + assert!(tm.tm_year == 0); + assert!(tm.tm_wday == 0); + assert!(tm.tm_isdst == 0); + assert!(tm.tm_utcoff == 0); + assert!(tm.tm_nsec == 0); + } + Err(_) => () + } + + let format = "%a %b %e %T.%f %Y"; + assert_eq!(strptime("", format), Err(ParseError::InvalidDay)); + assert_eq!(strptime("Fri Feb 13 15:31:30", format), + Err(InvalidTime)); + + match strptime("Fri Feb 13 15:31:30.01234 2009", format) { + Err(e) => panic!("{}", e), + Ok(ref tm) => { + assert_eq!(tm.tm_sec, 30); + assert_eq!(tm.tm_min, 31); + assert_eq!(tm.tm_hour, 15); + assert_eq!(tm.tm_mday, 13); + assert_eq!(tm.tm_mon, 1); + assert_eq!(tm.tm_year, 109); + assert_eq!(tm.tm_wday, 5); + assert_eq!(tm.tm_yday, 0); + assert_eq!(tm.tm_isdst, 0); + assert_eq!(tm.tm_utcoff, 0); + assert_eq!(tm.tm_nsec, 12340000); + } + } + + fn test(s: &str, format: &str) -> bool { + match strptime(s, format) { + Ok(tm) => { + tm.strftime(format).unwrap().to_string() == s.to_string() + }, + Err(e) => panic!("{:?}, s={:?}, format={:?}", e, s, format) + } + } + + fn test_oneway(s : &str, format : &str) -> bool { + match strptime(s, format) { + Ok(_) => { + // oneway tests are used when reformatting the parsed Tm + // back into a string can generate a different string + // from the original (i.e. leading zeroes) + true + }, + Err(e) => panic!("{:?}, s={:?}, format={:?}", e, s, format) + } + } + + let days = [ + "Sunday".to_string(), + "Monday".to_string(), + "Tuesday".to_string(), + "Wednesday".to_string(), + "Thursday".to_string(), + "Friday".to_string(), + "Saturday".to_string() + ]; + for day in days.iter() { + assert!(test(&day, "%A")); + } + + let days = [ + "Sun".to_string(), + "Mon".to_string(), + "Tue".to_string(), + "Wed".to_string(), + "Thu".to_string(), + "Fri".to_string(), + "Sat".to_string() + ]; + for day in days.iter() { + assert!(test(&day, "%a")); + } + + let months = [ + "January".to_string(), + "February".to_string(), + "March".to_string(), + "April".to_string(), + "May".to_string(), + "June".to_string(), + "July".to_string(), + "August".to_string(), + "September".to_string(), + "October".to_string(), + "November".to_string(), + "December".to_string() + ]; + for day in months.iter() { + assert!(test(&day, "%B")); + } + + let months = [ + "Jan".to_string(), + "Feb".to_string(), + "Mar".to_string(), + "Apr".to_string(), + "May".to_string(), + "Jun".to_string(), + "Jul".to_string(), + "Aug".to_string(), + "Sep".to_string(), + "Oct".to_string(), + "Nov".to_string(), + "Dec".to_string() + ]; + for day in months.iter() { + assert!(test(&day, "%b")); + } + + assert!(test("19", "%C")); + assert!(test("Fri Feb 3 23:31:30 2009", "%c")); + assert!(test("Fri Feb 13 23:31:30 2009", "%c")); + assert!(test("02/13/09", "%D")); + assert!(test("03", "%d")); + assert!(test("13", "%d")); + assert!(test(" 3", "%e")); + assert!(test("13", "%e")); + assert!(test("2009-02-13", "%F")); + assert!(test("03", "%H")); + assert!(test("13", "%H")); + assert!(test("03", "%I")); // FIXME (#2350): flesh out + assert!(test("11", "%I")); // FIXME (#2350): flesh out + assert!(test("044", "%j")); + assert!(test(" 3", "%k")); + assert!(test("13", "%k")); + assert!(test(" 1", "%l")); + assert!(test("11", "%l")); + assert!(test("03", "%M")); + assert!(test("13", "%M")); + assert!(test("\n", "%n")); + assert!(test("am", "%P")); + assert!(test("pm", "%P")); + assert!(test("AM", "%p")); + assert!(test("PM", "%p")); + assert!(test("23:31", "%R")); + assert!(test("11:31:30 AM", "%r")); + assert!(test("11:31:30 PM", "%r")); + assert!(test("03", "%S")); + assert!(test("13", "%S")); + assert!(test("15:31:30", "%T")); + assert!(test("\t", "%t")); + assert!(test("1", "%u")); + assert!(test("7", "%u")); + assert!(test("13-Feb-2009", "%v")); + assert!(test("0", "%w")); + assert!(test("6", "%w")); + assert!(test("2009", "%Y")); + assert!(test("09", "%y")); + + assert!(test_oneway("3", "%d")); + assert!(test_oneway("3", "%H")); + assert!(test_oneway("3", "%e")); + assert!(test_oneway("3", "%M")); + assert!(test_oneway("3", "%S")); + + assert!(strptime("-0000", "%z").unwrap().tm_utcoff == 0); + assert!(strptime("-00:00", "%z").unwrap().tm_utcoff == 0); + assert!(strptime("Z", "%z").unwrap().tm_utcoff == 0); + assert_eq!(-28800, strptime("-0800", "%z").unwrap().tm_utcoff); + assert_eq!(-28800, strptime("-08:00", "%z").unwrap().tm_utcoff); + assert_eq!(28800, strptime("+0800", "%z").unwrap().tm_utcoff); + assert_eq!(28800, strptime("+08:00", "%z").unwrap().tm_utcoff); + assert_eq!(5400, strptime("+0130", "%z").unwrap().tm_utcoff); + assert_eq!(5400, strptime("+01:30", "%z").unwrap().tm_utcoff); + assert!(test("%", "%%")); + + // Test for #7256 + assert_eq!(strptime("360", "%Y-%m-%d"), Err(InvalidYear)); + + // Test for epoch seconds parsing + { + assert!(test("1428035610", "%s")); + let tm = strptime("1428035610", "%s").unwrap(); + assert_eq!(tm.tm_utcoff, 0); + assert_eq!(tm.tm_isdst, 0); + assert_eq!(tm.tm_yday, 92); + assert_eq!(tm.tm_wday, 5); + assert_eq!(tm.tm_year, 115); + assert_eq!(tm.tm_mon, 3); + assert_eq!(tm.tm_mday, 3); + assert_eq!(tm.tm_hour, 4); + } + } + + #[test] + fn test_asctime() { + let _reset = set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + let local = at(time); + + debug!("test_ctime: {} {}", utc.asctime(), local.asctime()); + + assert_eq!(utc.asctime().to_string(), "Fri Feb 13 23:31:30 2009".to_string()); + assert_eq!(local.asctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string()); + } + + #[test] + fn test_ctime() { + let _reset = set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + let local = at(time); + + debug!("test_ctime: {} {}", utc.ctime(), local.ctime()); + + assert_eq!(utc.ctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string()); + assert_eq!(local.ctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string()); + } + + #[test] + fn test_strftime() { + let _reset = set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + let local = at(time); + + assert_eq!(local.strftime("").unwrap().to_string(), "".to_string()); + assert_eq!(local.strftime("%A").unwrap().to_string(), "Friday".to_string()); + assert_eq!(local.strftime("%a").unwrap().to_string(), "Fri".to_string()); + assert_eq!(local.strftime("%B").unwrap().to_string(), "February".to_string()); + assert_eq!(local.strftime("%b").unwrap().to_string(), "Feb".to_string()); + assert_eq!(local.strftime("%C").unwrap().to_string(), "20".to_string()); + assert_eq!(local.strftime("%c").unwrap().to_string(), + "Fri Feb 13 15:31:30 2009".to_string()); + assert_eq!(local.strftime("%D").unwrap().to_string(), "02/13/09".to_string()); + assert_eq!(local.strftime("%d").unwrap().to_string(), "13".to_string()); + assert_eq!(local.strftime("%e").unwrap().to_string(), "13".to_string()); + assert_eq!(local.strftime("%F").unwrap().to_string(), "2009-02-13".to_string()); + assert_eq!(local.strftime("%f").unwrap().to_string(), "000054321".to_string()); + assert_eq!(local.strftime("%G").unwrap().to_string(), "2009".to_string()); + assert_eq!(local.strftime("%g").unwrap().to_string(), "09".to_string()); + assert_eq!(local.strftime("%H").unwrap().to_string(), "15".to_string()); + assert_eq!(local.strftime("%h").unwrap().to_string(), "Feb".to_string()); + assert_eq!(local.strftime("%I").unwrap().to_string(), "03".to_string()); + assert_eq!(local.strftime("%j").unwrap().to_string(), "044".to_string()); + assert_eq!(local.strftime("%k").unwrap().to_string(), "15".to_string()); + assert_eq!(local.strftime("%l").unwrap().to_string(), " 3".to_string()); + assert_eq!(local.strftime("%M").unwrap().to_string(), "31".to_string()); + assert_eq!(local.strftime("%m").unwrap().to_string(), "02".to_string()); + assert_eq!(local.strftime("%n").unwrap().to_string(), "\n".to_string()); + assert_eq!(local.strftime("%P").unwrap().to_string(), "pm".to_string()); + assert_eq!(local.strftime("%p").unwrap().to_string(), "PM".to_string()); + assert_eq!(local.strftime("%R").unwrap().to_string(), "15:31".to_string()); + assert_eq!(local.strftime("%r").unwrap().to_string(), "03:31:30 PM".to_string()); + assert_eq!(local.strftime("%S").unwrap().to_string(), "30".to_string()); + assert_eq!(local.strftime("%s").unwrap().to_string(), "1234567890".to_string()); + assert_eq!(local.strftime("%T").unwrap().to_string(), "15:31:30".to_string()); + assert_eq!(local.strftime("%t").unwrap().to_string(), "\t".to_string()); + assert_eq!(local.strftime("%U").unwrap().to_string(), "06".to_string()); + assert_eq!(local.strftime("%u").unwrap().to_string(), "5".to_string()); + assert_eq!(local.strftime("%V").unwrap().to_string(), "07".to_string()); + assert_eq!(local.strftime("%v").unwrap().to_string(), "13-Feb-2009".to_string()); + assert_eq!(local.strftime("%W").unwrap().to_string(), "06".to_string()); + assert_eq!(local.strftime("%w").unwrap().to_string(), "5".to_string()); + // FIXME (#2350): support locale + assert_eq!(local.strftime("%X").unwrap().to_string(), "15:31:30".to_string()); + // FIXME (#2350): support locale + assert_eq!(local.strftime("%x").unwrap().to_string(), "02/13/09".to_string()); + assert_eq!(local.strftime("%Y").unwrap().to_string(), "2009".to_string()); + assert_eq!(local.strftime("%y").unwrap().to_string(), "09".to_string()); + // FIXME (#2350): support locale + assert_eq!(local.strftime("%Z").unwrap().to_string(), "".to_string()); + assert_eq!(local.strftime("%z").unwrap().to_string(), "-0800".to_string()); + assert_eq!(local.strftime("%+").unwrap().to_string(), + "2009-02-13T15:31:30-08:00".to_string()); + assert_eq!(local.strftime("%%").unwrap().to_string(), "%".to_string()); + + let invalid_specifiers = ["%E", "%J", "%K", "%L", "%N", "%O", "%o", "%Q", "%q"]; + for &sp in invalid_specifiers.iter() { + assert_eq!(local.strftime(sp).unwrap_err(), + InvalidFormatSpecifier(sp[1..].chars().next().unwrap())); + } + assert_eq!(local.strftime("%").unwrap_err(), MissingFormatConverter); + assert_eq!(local.strftime("%A %").unwrap_err(), MissingFormatConverter); + + assert_eq!(local.asctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string()); + assert_eq!(local.ctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string()); + assert_eq!(local.rfc822z().to_string(), "Fri, 13 Feb 2009 15:31:30 -0800".to_string()); + assert_eq!(local.rfc3339().to_string(), "2009-02-13T15:31:30-08:00".to_string()); + + assert_eq!(utc.asctime().to_string(), "Fri Feb 13 23:31:30 2009".to_string()); + assert_eq!(utc.ctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string()); + assert_eq!(utc.rfc822().to_string(), "Fri, 13 Feb 2009 23:31:30 GMT".to_string()); + assert_eq!(utc.rfc822z().to_string(), "Fri, 13 Feb 2009 23:31:30 -0000".to_string()); + assert_eq!(utc.rfc3339().to_string(), "2009-02-13T23:31:30Z".to_string()); + } + + #[test] + fn test_timespec_eq_ord() { + let a = &Timespec::new(-2, 1); + let b = &Timespec::new(-1, 2); + let c = &Timespec::new(1, 2); + let d = &Timespec::new(2, 1); + let e = &Timespec::new(2, 1); + + assert!(d.eq(e)); + assert!(c.ne(e)); + + assert!(a.lt(b)); + assert!(b.lt(c)); + assert!(c.lt(d)); + + assert!(a.le(b)); + assert!(b.le(c)); + assert!(c.le(d)); + assert!(d.le(e)); + assert!(e.le(d)); + + assert!(b.ge(a)); + assert!(c.ge(b)); + assert!(d.ge(c)); + assert!(e.ge(d)); + assert!(d.ge(e)); + + assert!(b.gt(a)); + assert!(c.gt(b)); + assert!(d.gt(c)); + } + + #[test] + #[allow(deprecated)] + fn test_timespec_hash() { + use std::hash::{Hash, Hasher}; + + let c = &Timespec::new(3, 2); + let d = &Timespec::new(2, 1); + let e = &Timespec::new(2, 1); + + let mut hasher = ::std::hash::SipHasher::new(); + + let d_hash:u64 = { + d.hash(&mut hasher); + hasher.finish() + }; + + hasher = ::std::hash::SipHasher::new(); + + let e_hash:u64 = { + e.hash(&mut hasher); + hasher.finish() + }; + + hasher = ::std::hash::SipHasher::new(); + + let c_hash:u64 = { + c.hash(&mut hasher); + hasher.finish() + }; + + assert_eq!(d_hash, e_hash); + assert!(c_hash != e_hash); + } + + #[test] + fn test_timespec_add() { + let a = Timespec::new(1, 2); + let b = Duration::seconds(2) + Duration::nanoseconds(3); + let c = a + b; + assert_eq!(c.sec, 3); + assert_eq!(c.nsec, 5); + + let p = Timespec::new(1, super::NSEC_PER_SEC - 2); + let q = Duration::seconds(2) + Duration::nanoseconds(2); + let r = p + q; + assert_eq!(r.sec, 4); + assert_eq!(r.nsec, 0); + + let u = Timespec::new(1, super::NSEC_PER_SEC - 2); + let v = Duration::seconds(2) + Duration::nanoseconds(3); + let w = u + v; + assert_eq!(w.sec, 4); + assert_eq!(w.nsec, 1); + + let k = Timespec::new(1, 0); + let l = Duration::nanoseconds(-1); + let m = k + l; + assert_eq!(m.sec, 0); + assert_eq!(m.nsec, 999_999_999); + } + + #[test] + fn test_timespec_sub() { + let a = Timespec::new(2, 3); + let b = Timespec::new(1, 2); + let c = a - b; + assert_eq!(c.num_nanoseconds(), Some(super::NSEC_PER_SEC as i64 + 1)); + + let p = Timespec::new(2, 0); + let q = Timespec::new(1, 2); + let r = p - q; + assert_eq!(r.num_nanoseconds(), Some(super::NSEC_PER_SEC as i64 - 2)); + + let u = Timespec::new(1, 2); + let v = Timespec::new(2, 3); + let w = u - v; + assert_eq!(w.num_nanoseconds(), Some(-super::NSEC_PER_SEC as i64 - 1)); + } + + #[test] + fn test_time_sub() { + let a = ::now(); + let b = at(a.to_timespec() + Duration::seconds(5)); + let c = b - a; + assert_eq!(c.num_nanoseconds(), Some(super::NSEC_PER_SEC as i64 * 5)); + } + + #[test] + fn test_steadytime_sub() { + let a = SteadyTime::now(); + let b = a + Duration::seconds(1); + assert_eq!(b - a, Duration::seconds(1)); + assert_eq!(a - b, Duration::seconds(-1)); + } + + #[test] + fn test_date_before_1970() { + let early = strptime("1901-01-06", "%F").unwrap(); + let late = strptime("2000-01-01", "%F").unwrap(); + assert!(early < late); + } + + #[test] + fn test_dst() { + let _reset = set_time_zone_london_dst(); + let utc_in_feb = strptime("2015-02-01Z", "%F%z").unwrap(); + let utc_in_jun = strptime("2015-06-01Z", "%F%z").unwrap(); + let utc_in_nov = strptime("2015-11-01Z", "%F%z").unwrap(); + let local_in_feb = utc_in_feb.to_local(); + let local_in_jun = utc_in_jun.to_local(); + let local_in_nov = utc_in_nov.to_local(); + + assert_eq!(local_in_feb.tm_mon, 1); + assert_eq!(local_in_feb.tm_hour, 0); + assert_eq!(local_in_feb.tm_utcoff, 0); + assert_eq!(local_in_feb.tm_isdst, 0); + + assert_eq!(local_in_jun.tm_mon, 5); + assert_eq!(local_in_jun.tm_hour, 1); + assert_eq!(local_in_jun.tm_utcoff, 3600); + assert_eq!(local_in_jun.tm_isdst, 1); + + assert_eq!(local_in_nov.tm_mon, 10); + assert_eq!(local_in_nov.tm_hour, 0); + assert_eq!(local_in_nov.tm_utcoff, 0); + assert_eq!(local_in_nov.tm_isdst, 0) + } +} diff --git a/third_party/rust/time-0.1.45/src/parse.rs b/third_party/rust/time-0.1.45/src/parse.rs new file mode 100644 index 0000000000..602bdc5c76 --- /dev/null +++ b/third_party/rust/time-0.1.45/src/parse.rs @@ -0,0 +1,395 @@ +use super::{Timespec, Tm, at_utc, ParseError, NSEC_PER_SEC}; + +/// Parses the time from the string according to the format string. +pub fn strptime(mut s: &str, format: &str) -> Result<Tm, ParseError> { + let mut tm = Tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_utcoff: 0, + tm_nsec: 0, + }; + let mut chars = format.chars(); + + while let Some(ch) = chars.next() { + if ch == '%' { + if let Some(ch) = chars.next() { + parse_type(&mut s, ch, &mut tm)?; + } + } else { + parse_char(&mut s, ch)?; + } + } + + Ok(tm) +} + +fn parse_type(s: &mut &str, ch: char, tm: &mut Tm) -> Result<(), ParseError> { + match ch { + 'A' => match match_strs(s, &[("Sunday", 0), + ("Monday", 1), + ("Tuesday", 2), + ("Wednesday", 3), + ("Thursday", 4), + ("Friday", 5), + ("Saturday", 6)]) { + Some(v) => { tm.tm_wday = v; Ok(()) } + None => Err(ParseError::InvalidDay) + }, + 'a' => match match_strs(s, &[("Sun", 0), + ("Mon", 1), + ("Tue", 2), + ("Wed", 3), + ("Thu", 4), + ("Fri", 5), + ("Sat", 6)]) { + Some(v) => { tm.tm_wday = v; Ok(()) } + None => Err(ParseError::InvalidDay) + }, + 'B' => match match_strs(s, &[("January", 0), + ("February", 1), + ("March", 2), + ("April", 3), + ("May", 4), + ("June", 5), + ("July", 6), + ("August", 7), + ("September", 8), + ("October", 9), + ("November", 10), + ("December", 11)]) { + Some(v) => { tm.tm_mon = v; Ok(()) } + None => Err(ParseError::InvalidMonth) + }, + 'b' | 'h' => match match_strs(s, &[("Jan", 0), + ("Feb", 1), + ("Mar", 2), + ("Apr", 3), + ("May", 4), + ("Jun", 5), + ("Jul", 6), + ("Aug", 7), + ("Sep", 8), + ("Oct", 9), + ("Nov", 10), + ("Dec", 11)]) { + Some(v) => { tm.tm_mon = v; Ok(()) } + None => Err(ParseError::InvalidMonth) + }, + 'C' => match match_digits_in_range(s, 1, 2, false, 0, 99) { + Some(v) => { tm.tm_year += (v * 100) - 1900; Ok(()) } + None => Err(ParseError::InvalidYear) + }, + 'c' => { + parse_type(s, 'a', tm) + .and_then(|()| parse_char(s, ' ')) + .and_then(|()| parse_type(s, 'b', tm)) + .and_then(|()| parse_char(s, ' ')) + .and_then(|()| parse_type(s, 'e', tm)) + .and_then(|()| parse_char(s, ' ')) + .and_then(|()| parse_type(s, 'T', tm)) + .and_then(|()| parse_char(s, ' ')) + .and_then(|()| parse_type(s, 'Y', tm)) + } + 'D' | 'x' => { + parse_type(s, 'm', tm) + .and_then(|()| parse_char(s, '/')) + .and_then(|()| parse_type(s, 'd', tm)) + .and_then(|()| parse_char(s, '/')) + .and_then(|()| parse_type(s, 'y', tm)) + } + 'd' => match match_digits_in_range(s, 1, 2, false, 1, 31) { + Some(v) => { tm.tm_mday = v; Ok(()) } + None => Err(ParseError::InvalidDayOfMonth) + }, + 'e' => match match_digits_in_range(s, 1, 2, true, 1, 31) { + Some(v) => { tm.tm_mday = v; Ok(()) } + None => Err(ParseError::InvalidDayOfMonth) + }, + 'f' => { + tm.tm_nsec = match_fractional_seconds(s); + Ok(()) + } + 'F' => { + parse_type(s, 'Y', tm) + .and_then(|()| parse_char(s, '-')) + .and_then(|()| parse_type(s, 'm', tm)) + .and_then(|()| parse_char(s, '-')) + .and_then(|()| parse_type(s, 'd', tm)) + } + 'H' => { + match match_digits_in_range(s, 1, 2, false, 0, 23) { + Some(v) => { tm.tm_hour = v; Ok(()) } + None => Err(ParseError::InvalidHour) + } + } + 'I' => { + match match_digits_in_range(s, 1, 2, false, 1, 12) { + Some(v) => { tm.tm_hour = if v == 12 { 0 } else { v }; Ok(()) } + None => Err(ParseError::InvalidHour) + } + } + 'j' => { + match match_digits_in_range(s, 1, 3, false, 1, 366) { + Some(v) => { tm.tm_yday = v - 1; Ok(()) } + None => Err(ParseError::InvalidDayOfYear) + } + } + 'k' => { + match match_digits_in_range(s, 1, 2, true, 0, 23) { + Some(v) => { tm.tm_hour = v; Ok(()) } + None => Err(ParseError::InvalidHour) + } + } + 'l' => { + match match_digits_in_range(s, 1, 2, true, 1, 12) { + Some(v) => { tm.tm_hour = if v == 12 { 0 } else { v }; Ok(()) } + None => Err(ParseError::InvalidHour) + } + } + 'M' => { + match match_digits_in_range(s, 1, 2, false, 0, 59) { + Some(v) => { tm.tm_min = v; Ok(()) } + None => Err(ParseError::InvalidMinute) + } + } + 'm' => { + match match_digits_in_range(s, 1, 2, false, 1, 12) { + Some(v) => { tm.tm_mon = v - 1; Ok(()) } + None => Err(ParseError::InvalidMonth) + } + } + 'n' => parse_char(s, '\n'), + 'P' => match match_strs(s, &[("am", 0), ("pm", 12)]) { + Some(v) => { tm.tm_hour += v; Ok(()) } + None => Err(ParseError::InvalidHour) + }, + 'p' => match match_strs(s, &[("AM", 0), ("PM", 12)]) { + Some(v) => { tm.tm_hour += v; Ok(()) } + None => Err(ParseError::InvalidHour) + }, + 'R' => { + parse_type(s, 'H', tm) + .and_then(|()| parse_char(s, ':')) + .and_then(|()| parse_type(s, 'M', tm)) + } + 'r' => { + parse_type(s, 'I', tm) + .and_then(|()| parse_char(s, ':')) + .and_then(|()| parse_type(s, 'M', tm)) + .and_then(|()| parse_char(s, ':')) + .and_then(|()| parse_type(s, 'S', tm)) + .and_then(|()| parse_char(s, ' ')) + .and_then(|()| parse_type(s, 'p', tm)) + } + 's' => { + match match_digits_i64(s, 1, 18, false) { + Some(v) => { + *tm = at_utc(Timespec::new(v, 0)); + Ok(()) + }, + None => Err(ParseError::InvalidSecondsSinceEpoch) + } + } + 'S' => { + match match_digits_in_range(s, 1, 2, false, 0, 60) { + Some(v) => { tm.tm_sec = v; Ok(()) } + None => Err(ParseError::InvalidSecond) + } + } + //'s' {} + 'T' | 'X' => { + parse_type(s, 'H', tm) + .and_then(|()| parse_char(s, ':')) + .and_then(|()| parse_type(s, 'M', tm)) + .and_then(|()| parse_char(s, ':')) + .and_then(|()| parse_type(s, 'S', tm)) + } + 't' => parse_char(s, '\t'), + 'u' => { + match match_digits_in_range(s, 1, 1, false, 1, 7) { + Some(v) => { tm.tm_wday = if v == 7 { 0 } else { v }; Ok(()) } + None => Err(ParseError::InvalidDayOfWeek) + } + } + 'v' => { + parse_type(s, 'e', tm) + .and_then(|()| parse_char(s, '-')) + .and_then(|()| parse_type(s, 'b', tm)) + .and_then(|()| parse_char(s, '-')) + .and_then(|()| parse_type(s, 'Y', tm)) + } + //'W' {} + 'w' => { + match match_digits_in_range(s, 1, 1, false, 0, 6) { + Some(v) => { tm.tm_wday = v; Ok(()) } + None => Err(ParseError::InvalidDayOfWeek) + } + } + 'Y' => { + match match_digits(s, 4, 4, false) { + Some(v) => { tm.tm_year = v - 1900; Ok(()) } + None => Err(ParseError::InvalidYear) + } + } + 'y' => { + match match_digits_in_range(s, 1, 2, false, 0, 99) { + Some(v) => { tm.tm_year = v; Ok(()) } + None => Err(ParseError::InvalidYear) + } + } + 'Z' => { + if match_str(s, "UTC") || match_str(s, "GMT") { + tm.tm_utcoff = 0; + Ok(()) + } else { + // It's odd, but to maintain compatibility with c's + // strptime we ignore the timezone. + for (i, ch) in s.char_indices() { + if ch == ' ' { + *s = &s[i..]; + return Ok(()) + } + } + *s = ""; + Ok(()) + } + } + 'z' => { + if parse_char(s, 'Z').is_ok() { + tm.tm_utcoff = 0; + Ok(()) + } else { + let sign = if parse_char(s, '+').is_ok() {1} + else if parse_char(s, '-').is_ok() {-1} + else { return Err(ParseError::InvalidZoneOffset) }; + + let hours; + let minutes; + + match match_digits(s, 2, 2, false) { + Some(h) => hours = h, + None => return Err(ParseError::InvalidZoneOffset) + } + + // consume the colon if its present, + // just ignore it otherwise + let _ = parse_char(s, ':'); + + match match_digits(s, 2, 2, false) { + Some(m) => minutes = m, + None => return Err(ParseError::InvalidZoneOffset) + } + + tm.tm_utcoff = sign * (hours * 60 * 60 + minutes * 60); + Ok(()) + } + } + '%' => parse_char(s, '%'), + ch => Err(ParseError::InvalidFormatSpecifier(ch)) + } +} + + +fn match_str(s: &mut &str, needle: &str) -> bool { + if s.starts_with(needle) { + *s = &s[needle.len()..]; + true + } else { + false + } +} + +fn match_strs(ss: &mut &str, strs: &[(&str, i32)]) -> Option<i32> { + for &(needle, value) in strs.iter() { + if match_str(ss, needle) { + return Some(value) + } + } + None +} + +fn match_digits(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option<i32> { + match match_digits_i64(ss, min_digits, max_digits, ws) { + Some(v) => Some(v as i32), + None => None + } +} + +fn match_digits_i64(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option<i64> { + let mut value : i64 = 0; + let mut n = 0; + if ws { + #[allow(deprecated)] // use `trim_start_matches` starting in 1.30 + let s2 = ss.trim_left_matches(" "); + n = ss.len() - s2.len(); + if n > max_digits { return None } + } + let chars = ss[n..].char_indices(); + for (_, ch) in chars.take(max_digits - n) { + match ch { + '0' ... '9' => value = value * 10 + (ch as i64 - '0' as i64), + _ => break, + } + n += 1; + } + + if n >= min_digits && n <= max_digits { + *ss = &ss[n..]; + Some(value) + } else { + None + } +} + +fn match_fractional_seconds(ss: &mut &str) -> i32 { + let mut value = 0; + let mut multiplier = NSEC_PER_SEC / 10; + + let mut chars = ss.char_indices(); + let orig = *ss; + for (i, ch) in &mut chars { + *ss = &orig[i..]; + match ch { + '0' ... '9' => { + // This will drop digits after the nanoseconds place + let digit = ch as i32 - '0' as i32; + value += digit * multiplier; + multiplier /= 10; + } + _ => break + } + } + + value +} + +fn match_digits_in_range(ss: &mut &str, + min_digits : usize, max_digits : usize, + ws: bool, min: i32, max: i32) -> Option<i32> { + let before = *ss; + match match_digits(ss, min_digits, max_digits, ws) { + Some(val) if val >= min && val <= max => Some(val), + _ => { *ss = before; None } + } +} + +fn parse_char(s: &mut &str, c: char) -> Result<(), ParseError> { + match s.char_indices().next() { + Some((i, c2)) => { + if c == c2 { + *s = &s[i + c2.len_utf8()..]; + Ok(()) + } else { + Err(ParseError::UnexpectedCharacter(c, c2)) + } + } + None => Err(ParseError::InvalidTime), + } +} diff --git a/third_party/rust/time-0.1.45/src/sys.rs b/third_party/rust/time-0.1.45/src/sys.rs new file mode 100644 index 0000000000..c5765ca895 --- /dev/null +++ b/third_party/rust/time-0.1.45/src/sys.rs @@ -0,0 +1,996 @@ +#![allow(bad_style)] + +pub use self::inner::*; + +#[cfg(any( + all(target_arch = "wasm32", not(target_os = "emscripten")), + target_env = "sgx" +))] +mod common { + use Tm; + + pub fn time_to_tm(ts: i64, tm: &mut Tm) { + let leapyear = |year| -> bool { + year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) + }; + + static _ytab: [[i64; 12]; 2] = [ + [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ], + [ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ] + ]; + + let mut year = 1970; + + let dayclock = ts % 86400; + let mut dayno = ts / 86400; + + tm.tm_sec = (dayclock % 60) as i32; + tm.tm_min = ((dayclock % 3600) / 60) as i32; + tm.tm_hour = (dayclock / 3600) as i32; + tm.tm_wday = ((dayno + 4) % 7) as i32; + loop { + let yearsize = if leapyear(year) { + 366 + } else { + 365 + }; + if dayno >= yearsize { + dayno -= yearsize; + year += 1; + } else { + break; + } + } + tm.tm_year = (year - 1900) as i32; + tm.tm_yday = dayno as i32; + let mut mon = 0; + while dayno >= _ytab[if leapyear(year) { 1 } else { 0 }][mon] { + dayno -= _ytab[if leapyear(year) { 1 } else { 0 }][mon]; + mon += 1; + } + tm.tm_mon = mon as i32; + tm.tm_mday = dayno as i32 + 1; + tm.tm_isdst = 0; + } + + pub fn tm_to_time(tm: &Tm) -> i64 { + let mut y = tm.tm_year as i64 + 1900; + let mut m = tm.tm_mon as i64 + 1; + if m <= 2 { + y -= 1; + m += 12; + } + let d = tm.tm_mday as i64; + let h = tm.tm_hour as i64; + let mi = tm.tm_min as i64; + let s = tm.tm_sec as i64; + (365*y + y/4 - y/100 + y/400 + 3*(m+1)/5 + 30*m + d - 719561) + * 86400 + 3600 * h + 60 * mi + s + } +} + +#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] +mod inner { + use std::ops::{Add, Sub}; + use Tm; + use Duration; + use super::common::{time_to_tm, tm_to_time}; + + #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] + pub struct SteadyTime; + + pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) { + time_to_tm(sec, tm); + } + + pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { + // FIXME: Add timezone logic + time_to_tm(sec, tm); + } + + pub fn utc_tm_to_time(tm: &Tm) -> i64 { + tm_to_time(tm) + } + + pub fn local_tm_to_time(tm: &Tm) -> i64 { + // FIXME: Add timezone logic + tm_to_time(tm) + } + + pub fn get_time() -> (i64, i32) { + unimplemented!() + } + + pub fn get_precise_ns() -> u64 { + unimplemented!() + } + + impl SteadyTime { + pub fn now() -> SteadyTime { + unimplemented!() + } + } + + impl Sub for SteadyTime { + type Output = Duration; + fn sub(self, _other: SteadyTime) -> Duration { + unimplemented!() + } + } + + impl Sub<Duration> for SteadyTime { + type Output = SteadyTime; + fn sub(self, _other: Duration) -> SteadyTime { + unimplemented!() + } + } + + impl Add<Duration> for SteadyTime { + type Output = SteadyTime; + fn add(self, _other: Duration) -> SteadyTime { + unimplemented!() + } + } +} + +#[cfg(target_os = "wasi")] +mod inner { + use std::ops::{Add, Sub}; + use Tm; + use Duration; + use super::common::{time_to_tm, tm_to_time}; + use wasi::{clock_time_get, CLOCKID_MONOTONIC, CLOCKID_REALTIME}; + + #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] + pub struct SteadyTime { + t: u64 + } + + pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) { + time_to_tm(sec, tm); + } + + pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { + // FIXME: Add timezone logic + time_to_tm(sec, tm); + } + + pub fn utc_tm_to_time(tm: &Tm) -> i64 { + tm_to_time(tm) + } + + pub fn local_tm_to_time(tm: &Tm) -> i64 { + // FIXME: Add timezone logic + tm_to_time(tm) + } + + pub fn get_time() -> (i64, i32) { + let ts = get_precise_ns(); + ( + ts as i64 / 1_000_000_000, + (ts as i64 % 1_000_000_000) as i32, + ) + } + + pub fn get_precise_ns() -> u64 { + unsafe { clock_time_get(CLOCKID_REALTIME, 1_000_000_000) } + .expect("Host doesn't implement a real-time clock") + } + + impl SteadyTime { + pub fn now() -> SteadyTime { + SteadyTime { + t: unsafe { clock_time_get(CLOCKID_MONOTONIC, 1_000_000_000) } + .expect("Host doesn't implement a monotonic clock"), + } + } + } + + impl Sub for SteadyTime { + type Output = Duration; + fn sub(self, other: SteadyTime) -> Duration { + Duration::nanoseconds(self.t as i64 - other.t as i64) + } + } + + impl Sub<Duration> for SteadyTime { + type Output = SteadyTime; + fn sub(self, other: Duration) -> SteadyTime { + self + -other + } + } + + impl Add<Duration> for SteadyTime { + type Output = SteadyTime; + fn add(self, other: Duration) -> SteadyTime { + let delta = other.num_nanoseconds().unwrap(); + SteadyTime { + t: (self.t as i64 + delta) as u64, + } + } + } +} + +#[cfg(target_env = "sgx")] +mod inner { + use std::ops::{Add, Sub}; + use Tm; + use Duration; + use super::common::{time_to_tm, tm_to_time}; + use std::time::SystemTime; + + /// The number of nanoseconds in seconds. + const NANOS_PER_SEC: u64 = 1_000_000_000; + + #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] + pub struct SteadyTime { + t: Duration + } + + pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) { + time_to_tm(sec, tm); + } + + pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { + // FIXME: Add timezone logic + time_to_tm(sec, tm); + } + + pub fn utc_tm_to_time(tm: &Tm) -> i64 { + tm_to_time(tm) + } + + pub fn local_tm_to_time(tm: &Tm) -> i64 { + // FIXME: Add timezone logic + tm_to_time(tm) + } + + pub fn get_time() -> (i64, i32) { + SteadyTime::now().t.raw() + } + + pub fn get_precise_ns() -> u64 { + // This unwrap is safe because current time is well ahead of UNIX_EPOCH, unless system + // clock is adjusted backward. + let std_duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + std_duration.as_secs() * NANOS_PER_SEC + std_duration.subsec_nanos() as u64 + } + + impl SteadyTime { + pub fn now() -> SteadyTime { + // This unwrap is safe because current time is well ahead of UNIX_EPOCH, unless system + // clock is adjusted backward. + let std_duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + // This unwrap is safe because duration is well within the limits of i64. + let duration = Duration::from_std(std_duration).unwrap(); + SteadyTime { t: duration } + } + } + + impl Sub for SteadyTime { + type Output = Duration; + fn sub(self, other: SteadyTime) -> Duration { + self.t - other.t + } + } + + impl Sub<Duration> for SteadyTime { + type Output = SteadyTime; + fn sub(self, other: Duration) -> SteadyTime { + SteadyTime { t: self.t - other } + } + } + + impl Add<Duration> for SteadyTime { + type Output = SteadyTime; + fn add(self, other: Duration) -> SteadyTime { + SteadyTime { t: self.t + other } + } + } +} + +#[cfg(unix)] +mod inner { + use libc::{self, time_t}; + use std::mem; + use std::io; + use Tm; + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub use self::mac::*; + #[cfg(all(not(target_os = "macos"), not(target_os = "ios")))] + pub use self::unix::*; + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + extern { + static timezone: time_t; + static altzone: time_t; + } + + fn rust_tm_to_tm(rust_tm: &Tm, tm: &mut libc::tm) { + tm.tm_sec = rust_tm.tm_sec; + tm.tm_min = rust_tm.tm_min; + tm.tm_hour = rust_tm.tm_hour; + tm.tm_mday = rust_tm.tm_mday; + tm.tm_mon = rust_tm.tm_mon; + tm.tm_year = rust_tm.tm_year; + tm.tm_wday = rust_tm.tm_wday; + tm.tm_yday = rust_tm.tm_yday; + tm.tm_isdst = rust_tm.tm_isdst; + } + + fn tm_to_rust_tm(tm: &libc::tm, utcoff: i32, rust_tm: &mut Tm) { + rust_tm.tm_sec = tm.tm_sec; + rust_tm.tm_min = tm.tm_min; + rust_tm.tm_hour = tm.tm_hour; + rust_tm.tm_mday = tm.tm_mday; + rust_tm.tm_mon = tm.tm_mon; + rust_tm.tm_year = tm.tm_year; + rust_tm.tm_wday = tm.tm_wday; + rust_tm.tm_yday = tm.tm_yday; + rust_tm.tm_isdst = tm.tm_isdst; + rust_tm.tm_utcoff = utcoff; + } + + #[cfg(any(target_os = "nacl", target_os = "solaris", target_os = "illumos"))] + unsafe fn timegm(tm: *mut libc::tm) -> time_t { + use std::env::{set_var, var_os, remove_var}; + extern { + fn tzset(); + } + + let ret; + + let current_tz = var_os("TZ"); + set_var("TZ", "UTC"); + tzset(); + + ret = libc::mktime(tm); + + if let Some(tz) = current_tz { + set_var("TZ", tz); + } else { + remove_var("TZ"); + } + tzset(); + + ret + } + + pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) { + unsafe { + let sec = sec as time_t; + let mut out = mem::zeroed(); + if libc::gmtime_r(&sec, &mut out).is_null() { + panic!("gmtime_r failed: {}", io::Error::last_os_error()); + } + tm_to_rust_tm(&out, 0, tm); + } + } + + pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { + unsafe { + let sec = sec as time_t; + let mut out = mem::zeroed(); + if libc::localtime_r(&sec, &mut out).is_null() { + panic!("localtime_r failed: {}", io::Error::last_os_error()); + } + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + let gmtoff = { + ::tzset(); + // < 0 means we don't know; assume we're not in DST. + if out.tm_isdst == 0 { + // timezone is seconds west of UTC, tm_gmtoff is seconds east + -timezone + } else if out.tm_isdst > 0 { + -altzone + } else { + -timezone + } + }; + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + let gmtoff = out.tm_gmtoff; + tm_to_rust_tm(&out, gmtoff as i32, tm); + } + } + + pub fn utc_tm_to_time(rust_tm: &Tm) -> i64 { + #[cfg(all(target_os = "android", target_pointer_width = "32"))] + use libc::timegm64 as timegm; + #[cfg(not(any( + all(target_os = "android", target_pointer_width = "32"), + target_os = "nacl", + target_os = "solaris", + target_os = "illumos" + )))] + use libc::timegm; + + let mut tm = unsafe { mem::zeroed() }; + rust_tm_to_tm(rust_tm, &mut tm); + unsafe { timegm(&mut tm) as i64 } + } + + pub fn local_tm_to_time(rust_tm: &Tm) -> i64 { + let mut tm = unsafe { mem::zeroed() }; + rust_tm_to_tm(rust_tm, &mut tm); + unsafe { libc::mktime(&mut tm) as i64 } + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + mod mac { + #[allow(deprecated)] + use libc::{self, timeval, mach_timebase_info}; + #[allow(deprecated)] + use std::sync::{Once, ONCE_INIT}; + use std::ops::{Add, Sub}; + use Duration; + + #[allow(deprecated)] + fn info() -> &'static mach_timebase_info { + static mut INFO: mach_timebase_info = mach_timebase_info { + numer: 0, + denom: 0, + }; + static ONCE: Once = ONCE_INIT; + + unsafe { + ONCE.call_once(|| { + mach_timebase_info(&mut INFO); + }); + &INFO + } + } + + pub fn get_time() -> (i64, i32) { + use std::ptr; + let mut tv = timeval { tv_sec: 0, tv_usec: 0 }; + unsafe { libc::gettimeofday(&mut tv, ptr::null_mut()); } + (tv.tv_sec as i64, tv.tv_usec * 1000) + } + + #[allow(deprecated)] + #[inline] + pub fn get_precise_ns() -> u64 { + unsafe { + let time = libc::mach_absolute_time(); + let info = info(); + time * info.numer as u64 / info.denom as u64 + } + } + + #[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)] + pub struct SteadyTime { t: u64 } + + impl SteadyTime { + pub fn now() -> SteadyTime { + SteadyTime { t: get_precise_ns() } + } + } + impl Sub for SteadyTime { + type Output = Duration; + fn sub(self, other: SteadyTime) -> Duration { + Duration::nanoseconds(self.t as i64 - other.t as i64) + } + } + impl Sub<Duration> for SteadyTime { + type Output = SteadyTime; + fn sub(self, other: Duration) -> SteadyTime { + self + -other + } + } + impl Add<Duration> for SteadyTime { + type Output = SteadyTime; + fn add(self, other: Duration) -> SteadyTime { + let delta = other.num_nanoseconds().unwrap(); + SteadyTime { + t: (self.t as i64 + delta) as u64 + } + } + } + } + + #[cfg(test)] + pub struct TzReset; + + #[cfg(test)] + pub fn set_los_angeles_time_zone() -> TzReset { + use std::env; + env::set_var("TZ", "America/Los_Angeles"); + ::tzset(); + TzReset + } + + #[cfg(test)] + pub fn set_london_with_dst_time_zone() -> TzReset { + use std::env; + env::set_var("TZ", "Europe/London"); + ::tzset(); + TzReset + } + + #[cfg(all(not(target_os = "macos"), not(target_os = "ios")))] + mod unix { + use std::fmt; + use std::cmp::Ordering; + use std::mem::zeroed; + use std::ops::{Add, Sub}; + use libc; + + use Duration; + + pub fn get_time() -> (i64, i32) { + // SAFETY: libc::timespec is zero initializable. + let mut tv: libc::timespec = unsafe { zeroed() }; + unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut tv); } + (tv.tv_sec as i64, tv.tv_nsec as i32) + } + + pub fn get_precise_ns() -> u64 { + // SAFETY: libc::timespec is zero initializable. + let mut ts: libc::timespec = unsafe { zeroed() }; + unsafe { + libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts); + } + (ts.tv_sec as u64) * 1000000000 + (ts.tv_nsec as u64) + } + + #[derive(Copy)] + pub struct SteadyTime { + t: libc::timespec, + } + + impl fmt::Debug for SteadyTime { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "SteadyTime {{ tv_sec: {:?}, tv_nsec: {:?} }}", + self.t.tv_sec, self.t.tv_nsec) + } + } + + impl Clone for SteadyTime { + fn clone(&self) -> SteadyTime { + SteadyTime { t: self.t } + } + } + + impl SteadyTime { + pub fn now() -> SteadyTime { + let mut t = SteadyTime { + // SAFETY: libc::timespec is zero initializable. + t: unsafe { zeroed() } + }; + unsafe { + assert_eq!(0, libc::clock_gettime(libc::CLOCK_MONOTONIC, + &mut t.t)); + } + t + } + } + + impl Sub for SteadyTime { + type Output = Duration; + fn sub(self, other: SteadyTime) -> Duration { + if self.t.tv_nsec >= other.t.tv_nsec { + Duration::seconds(self.t.tv_sec as i64 - other.t.tv_sec as i64) + + Duration::nanoseconds(self.t.tv_nsec as i64 - other.t.tv_nsec as i64) + } else { + Duration::seconds(self.t.tv_sec as i64 - 1 - other.t.tv_sec as i64) + + Duration::nanoseconds(self.t.tv_nsec as i64 + ::NSEC_PER_SEC as i64 - + other.t.tv_nsec as i64) + } + } + } + + impl Sub<Duration> for SteadyTime { + type Output = SteadyTime; + fn sub(self, other: Duration) -> SteadyTime { + self + -other + } + } + + impl Add<Duration> for SteadyTime { + type Output = SteadyTime; + fn add(mut self, other: Duration) -> SteadyTime { + let seconds = other.num_seconds(); + let nanoseconds = other - Duration::seconds(seconds); + let nanoseconds = nanoseconds.num_nanoseconds().unwrap(); + + #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))] + type nsec = i64; + #[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))] + type nsec = libc::c_long; + + self.t.tv_sec += seconds as libc::time_t; + self.t.tv_nsec += nanoseconds as nsec; + if self.t.tv_nsec >= ::NSEC_PER_SEC as nsec { + self.t.tv_nsec -= ::NSEC_PER_SEC as nsec; + self.t.tv_sec += 1; + } else if self.t.tv_nsec < 0 { + self.t.tv_sec -= 1; + self.t.tv_nsec += ::NSEC_PER_SEC as nsec; + } + self + } + } + + impl PartialOrd for SteadyTime { + fn partial_cmp(&self, other: &SteadyTime) -> Option<Ordering> { + Some(self.cmp(other)) + } + } + + impl Ord for SteadyTime { + fn cmp(&self, other: &SteadyTime) -> Ordering { + match self.t.tv_sec.cmp(&other.t.tv_sec) { + Ordering::Equal => self.t.tv_nsec.cmp(&other.t.tv_nsec), + ord => ord + } + } + } + + impl PartialEq for SteadyTime { + fn eq(&self, other: &SteadyTime) -> bool { + self.t.tv_sec == other.t.tv_sec && + self.t.tv_nsec == other.t.tv_nsec + } + } + + impl Eq for SteadyTime {} + + } +} + +#[cfg(windows)] +#[allow(non_snake_case)] +mod inner { + use std::io; + use std::mem; + #[allow(deprecated)] + use std::sync::{Once, ONCE_INIT}; + use std::ops::{Add, Sub}; + use {Tm, Duration}; + + use winapi::um::winnt::*; + use winapi::shared::minwindef::*; + use winapi::um::minwinbase::SYSTEMTIME; + use winapi::um::profileapi::*; + use winapi::um::timezoneapi::*; + use winapi::um::sysinfoapi::GetSystemTimeAsFileTime; + + fn frequency() -> i64 { + static mut FREQUENCY: i64 = 0; + #[allow(deprecated)] + static ONCE: Once = ONCE_INIT; + + unsafe { + ONCE.call_once(|| { + let mut l = i64_to_large_integer(0); + QueryPerformanceFrequency(&mut l); + FREQUENCY = large_integer_to_i64(l); + }); + FREQUENCY + } + } + + fn i64_to_large_integer(i: i64) -> LARGE_INTEGER { + unsafe { + let mut large_integer: LARGE_INTEGER = mem::zeroed(); + *large_integer.QuadPart_mut() = i; + large_integer + } + } + + fn large_integer_to_i64(l: LARGE_INTEGER) -> i64 { + unsafe { + *l.QuadPart() + } + } + + const HECTONANOSECS_IN_SEC: i64 = 10_000_000; + const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; + + fn time_to_file_time(sec: i64) -> FILETIME { + let t = (((sec * HECTONANOSECS_IN_SEC) + HECTONANOSEC_TO_UNIX_EPOCH)) as u64; + FILETIME { + dwLowDateTime: t as DWORD, + dwHighDateTime: (t >> 32) as DWORD + } + } + + fn file_time_as_u64(ft: &FILETIME) -> u64 { + ((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64) + } + + fn file_time_to_nsec(ft: &FILETIME) -> i32 { + let t = file_time_as_u64(ft) as i64; + ((t % HECTONANOSECS_IN_SEC) * 100) as i32 + } + + fn file_time_to_unix_seconds(ft: &FILETIME) -> i64 { + let t = file_time_as_u64(ft) as i64; + ((t - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC) as i64 + } + + fn system_time_to_file_time(sys: &SYSTEMTIME) -> FILETIME { + unsafe { + let mut ft = mem::zeroed(); + SystemTimeToFileTime(sys, &mut ft); + ft + } + } + + fn tm_to_system_time(tm: &Tm) -> SYSTEMTIME { + let mut sys: SYSTEMTIME = unsafe { mem::zeroed() }; + sys.wSecond = tm.tm_sec as WORD; + sys.wMinute = tm.tm_min as WORD; + sys.wHour = tm.tm_hour as WORD; + sys.wDay = tm.tm_mday as WORD; + sys.wDayOfWeek = tm.tm_wday as WORD; + sys.wMonth = (tm.tm_mon + 1) as WORD; + sys.wYear = (tm.tm_year + 1900) as WORD; + sys + } + + fn system_time_to_tm(sys: &SYSTEMTIME, tm: &mut Tm) { + tm.tm_sec = sys.wSecond as i32; + tm.tm_min = sys.wMinute as i32; + tm.tm_hour = sys.wHour as i32; + tm.tm_mday = sys.wDay as i32; + tm.tm_wday = sys.wDayOfWeek as i32; + tm.tm_mon = (sys.wMonth - 1) as i32; + tm.tm_year = (sys.wYear - 1900) as i32; + tm.tm_yday = yday(tm.tm_year, tm.tm_mon + 1, tm.tm_mday); + + fn yday(year: i32, month: i32, day: i32) -> i32 { + let leap = if month > 2 { + if year % 4 == 0 { 1 } else { 2 } + } else { + 0 + }; + let july = if month > 7 { 1 } else { 0 }; + + (month - 1) * 30 + month / 2 + (day - 1) - leap + july + } + } + + macro_rules! call { + ($name:ident($($arg:expr),*)) => { + if $name($($arg),*) == 0 { + panic!(concat!(stringify!($name), " failed with: {}"), + io::Error::last_os_error()); + } + } + } + + pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) { + let mut out = unsafe { mem::zeroed() }; + let ft = time_to_file_time(sec); + unsafe { + call!(FileTimeToSystemTime(&ft, &mut out)); + } + system_time_to_tm(&out, tm); + tm.tm_utcoff = 0; + } + + pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { + let ft = time_to_file_time(sec); + unsafe { + let mut utc = mem::zeroed(); + let mut local = mem::zeroed(); + call!(FileTimeToSystemTime(&ft, &mut utc)); + call!(SystemTimeToTzSpecificLocalTime(0 as *const _, + &mut utc, &mut local)); + system_time_to_tm(&local, tm); + + let local = system_time_to_file_time(&local); + let local_sec = file_time_to_unix_seconds(&local); + + let mut tz = mem::zeroed(); + GetTimeZoneInformation(&mut tz); + + // SystemTimeToTzSpecificLocalTime already applied the biases so + // check if it non standard + tm.tm_utcoff = (local_sec - sec) as i32; + tm.tm_isdst = if tm.tm_utcoff == -60 * (tz.Bias + tz.StandardBias) { + 0 + } else { + 1 + }; + } + } + + pub fn utc_tm_to_time(tm: &Tm) -> i64 { + unsafe { + let mut ft = mem::zeroed(); + let sys_time = tm_to_system_time(tm); + call!(SystemTimeToFileTime(&sys_time, &mut ft)); + file_time_to_unix_seconds(&ft) + } + } + + pub fn local_tm_to_time(tm: &Tm) -> i64 { + unsafe { + let mut ft = mem::zeroed(); + let mut utc = mem::zeroed(); + let mut sys_time = tm_to_system_time(tm); + call!(TzSpecificLocalTimeToSystemTime(0 as *mut _, + &mut sys_time, &mut utc)); + call!(SystemTimeToFileTime(&utc, &mut ft)); + file_time_to_unix_seconds(&ft) + } + } + + pub fn get_time() -> (i64, i32) { + unsafe { + let mut ft = mem::zeroed(); + GetSystemTimeAsFileTime(&mut ft); + (file_time_to_unix_seconds(&ft), file_time_to_nsec(&ft)) + } + } + + pub fn get_precise_ns() -> u64 { + let mut ticks = i64_to_large_integer(0); + unsafe { + assert!(QueryPerformanceCounter(&mut ticks) == 1); + } + mul_div_i64(large_integer_to_i64(ticks), 1000000000, frequency()) as u64 + + } + + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] + pub struct SteadyTime { + t: i64, + } + + impl SteadyTime { + pub fn now() -> SteadyTime { + let mut l = i64_to_large_integer(0); + unsafe { QueryPerformanceCounter(&mut l); } + SteadyTime { t : large_integer_to_i64(l) } + } + } + + impl Sub for SteadyTime { + type Output = Duration; + fn sub(self, other: SteadyTime) -> Duration { + let diff = self.t as i64 - other.t as i64; + Duration::nanoseconds(mul_div_i64(diff, 1000000000, + frequency())) + } + } + + impl Sub<Duration> for SteadyTime { + type Output = SteadyTime; + fn sub(self, other: Duration) -> SteadyTime { + self + -other + } + } + + impl Add<Duration> for SteadyTime { + type Output = SteadyTime; + fn add(mut self, other: Duration) -> SteadyTime { + self.t += (other.num_microseconds().unwrap() * frequency() / + 1_000_000) as i64; + self + } + } + + #[cfg(test)] + pub struct TzReset { + old: TIME_ZONE_INFORMATION, + } + + #[cfg(test)] + impl Drop for TzReset { + fn drop(&mut self) { + unsafe { + call!(SetTimeZoneInformation(&self.old)); + } + } + } + + #[cfg(test)] + pub fn set_los_angeles_time_zone() -> TzReset { + acquire_privileges(); + + unsafe { + let mut tz = mem::zeroed::<TIME_ZONE_INFORMATION>(); + GetTimeZoneInformation(&mut tz); + let ret = TzReset { old: tz }; + tz.Bias = 60 * 8; + call!(SetTimeZoneInformation(&tz)); + return ret + } + } + + #[cfg(test)] + pub fn set_london_with_dst_time_zone() -> TzReset { + acquire_privileges(); + + unsafe { + let mut tz = mem::zeroed::<TIME_ZONE_INFORMATION>(); + GetTimeZoneInformation(&mut tz); + let ret = TzReset { old: tz }; + // Since date set precisely this is 2015's dates + tz.Bias = 0; + tz.DaylightBias = -60; + tz.DaylightDate.wYear = 0; + tz.DaylightDate.wMonth = 3; + tz.DaylightDate.wDayOfWeek = 0; + tz.DaylightDate.wDay = 5; + tz.DaylightDate.wHour = 2; + tz.StandardBias = 0; + tz.StandardDate.wYear = 0; + tz.StandardDate.wMonth = 10; + tz.StandardDate.wDayOfWeek = 0; + tz.StandardDate.wDay = 5; + tz.StandardDate.wHour = 2; + call!(SetTimeZoneInformation(&tz)); + return ret + } + } + + // Ensures that this process has the necessary privileges to set a new time + // zone, and this is all transcribed from: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724944%28v=vs.85%29.aspx + #[cfg(test)] + fn acquire_privileges() { + use winapi::um::processthreadsapi::*; + use winapi::um::winbase::LookupPrivilegeValueA; + const SE_PRIVILEGE_ENABLED: DWORD = 2; + #[allow(deprecated)] + static INIT: Once = ONCE_INIT; + + // TODO: FIXME + extern "system" { + fn AdjustTokenPrivileges( + TokenHandle: HANDLE, DisableAllPrivileges: BOOL, NewState: PTOKEN_PRIVILEGES, + BufferLength: DWORD, PreviousState: PTOKEN_PRIVILEGES, ReturnLength: PDWORD, + ) -> BOOL; + } + + INIT.call_once(|| unsafe { + let mut hToken = 0 as *mut _; + call!(OpenProcessToken(GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + &mut hToken)); + + let mut tkp = mem::zeroed::<TOKEN_PRIVILEGES>(); + assert_eq!(tkp.Privileges.len(), 1); + let c = ::std::ffi::CString::new("SeTimeZonePrivilege").unwrap(); + call!(LookupPrivilegeValueA(0 as *const _, c.as_ptr(), + &mut tkp.Privileges[0].Luid)); + tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + tkp.PrivilegeCount = 1; + call!(AdjustTokenPrivileges(hToken, FALSE, &mut tkp, 0, + 0 as *mut _, 0 as *mut _)); + }); + } + + + + // Computes (value*numer)/denom without overflow, as long as both + // (numer*denom) and the overall result fit into i64 (which is the case + // for our time conversions). + fn mul_div_i64(value: i64, numer: i64, denom: i64) -> i64 { + let q = value / denom; + let r = value % denom; + // Decompose value as (value/denom*denom + value%denom), + // substitute into (value*numer)/denom and simplify. + // r < denom, so (denom*numer) is the upper bound of (r*numer) + q * numer + r * numer / denom + } + + #[test] + fn test_muldiv() { + assert_eq!(mul_div_i64( 1_000_000_000_001, 1_000_000_000, 1_000_000), + 1_000_000_000_001_000); + assert_eq!(mul_div_i64(-1_000_000_000_001, 1_000_000_000, 1_000_000), + -1_000_000_000_001_000); + assert_eq!(mul_div_i64(-1_000_000_000_001,-1_000_000_000, 1_000_000), + 1_000_000_000_001_000); + assert_eq!(mul_div_i64( 1_000_000_000_001, 1_000_000_000,-1_000_000), + -1_000_000_000_001_000); + assert_eq!(mul_div_i64( 1_000_000_000_001,-1_000_000_000,-1_000_000), + 1_000_000_000_001_000); + } +} diff --git a/third_party/rust/time-core/.cargo-checksum.json b/third_party/rust/time-core/.cargo-checksum.json new file mode 100644 index 0000000000..e58090e197 --- /dev/null +++ b/third_party/rust/time-core/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"d20a28a1ed2c35aff8a60f495a0ed440e022051d3909b372bf5e262fd62d7b29","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/lib.rs":"247b6ac4c2acc97e51552fd7ca1ef7cb2cbc30f8bcbbf5d029553a83a7fe2cc4","src/util.rs":"52c1fbf68b71c3582caf0d9a8255378c6c14a737e2df8d7e6d6603b0eb12ca06"},"package":"2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"}
\ No newline at end of file diff --git a/third_party/rust/time-core/Cargo.toml b/third_party/rust/time-core/Cargo.toml new file mode 100644 index 0000000000..574a387470 --- /dev/null +++ b/third_party/rust/time-core/Cargo.toml @@ -0,0 +1,32 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.60.0" +name = "time-core" +version = "0.1.0" +authors = [ + "Jacob Pratt <open-source@jhpratt.dev>", + "Time contributors", +] +description = "This crate is an implementation detail and should not be relied upon directly." +keywords = [ + "date", + "time", + "calendar", + "duration", +] +categories = ["date-and-time"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/time-rs/time" + +[dependencies] diff --git a/third_party/rust/time-core/LICENSE-Apache b/third_party/rust/time-core/LICENSE-Apache new file mode 100644 index 0000000000..7646f21e37 --- /dev/null +++ b/third_party/rust/time-core/LICENSE-Apache @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Jacob Pratt et al. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/rust/time-core/LICENSE-MIT b/third_party/rust/time-core/LICENSE-MIT new file mode 100644 index 0000000000..a11a755732 --- /dev/null +++ b/third_party/rust/time-core/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2022 Jacob Pratt et al. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/rust/time-core/src/lib.rs b/third_party/rust/time-core/src/lib.rs new file mode 100644 index 0000000000..d962578b59 --- /dev/null +++ b/third_party/rust/time-core/src/lib.rs @@ -0,0 +1,52 @@ +//! Core items for `time`. +//! +//! This crate is an implementation detail of `time` and should not be relied upon directly. + +#![no_std] +#![deny( + anonymous_parameters, + clippy::all, + clippy::alloc_instead_of_core, + clippy::explicit_auto_deref, + clippy::obfuscated_if_else, + clippy::std_instead_of_core, + clippy::undocumented_unsafe_blocks, + const_err, + illegal_floating_point_literal_pattern, + late_bound_lifetime_arguments, + path_statements, + patterns_in_fns_without_body, + rust_2018_idioms, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_extern_crates, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links +)] +#![warn( + clippy::dbg_macro, + clippy::decimal_literal_representation, + clippy::get_unwrap, + clippy::missing_docs_in_private_items, + clippy::nursery, + clippy::print_stdout, + clippy::todo, + clippy::unimplemented, + clippy::unnested_or_patterns, + clippy::unwrap_in_result, + clippy::unwrap_used, + clippy::use_debug, + deprecated_in_future, + missing_copy_implementations, + missing_debug_implementations, + unused_qualifications, + variant_size_differences +)] +#![allow(clippy::redundant_pub_crate)] +#![doc(html_favicon_url = "https://avatars0.githubusercontent.com/u/55999857")] +#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55999857")] +#![doc(test(attr(deny(warnings))))] + +pub mod util; diff --git a/third_party/rust/time-core/src/util.rs b/third_party/rust/time-core/src/util.rs new file mode 100644 index 0000000000..26ced0638c --- /dev/null +++ b/third_party/rust/time-core/src/util.rs @@ -0,0 +1,52 @@ +//! Utility functions. + +/// Returns if the provided year is a leap year in the proleptic Gregorian calendar. Uses +/// [astronomical year numbering](https://en.wikipedia.org/wiki/Astronomical_year_numbering). +/// +/// ```rust +/// # use time::util::is_leap_year; +/// assert!(!is_leap_year(1900)); +/// assert!(is_leap_year(2000)); +/// assert!(is_leap_year(2004)); +/// assert!(!is_leap_year(2005)); +/// assert!(!is_leap_year(2100)); +/// ``` +pub const fn is_leap_year(year: i32) -> bool { + year % 4 == 0 && (year % 25 != 0 || year % 16 == 0) +} + +/// Get the number of calendar days in a given year. +/// +/// The returned value will always be either 365 or 366. +/// +/// ```rust +/// # use time::util::days_in_year; +/// assert_eq!(days_in_year(1900), 365); +/// assert_eq!(days_in_year(2000), 366); +/// assert_eq!(days_in_year(2004), 366); +/// assert_eq!(days_in_year(2005), 365); +/// assert_eq!(days_in_year(2100), 365); +/// ``` +pub const fn days_in_year(year: i32) -> u16 { + if is_leap_year(year) { 366 } else { 365 } +} + +/// Get the number of weeks in the ISO year. +/// +/// The returned value will always be either 52 or 53. +/// +/// ```rust +/// # use time::util::weeks_in_year; +/// assert_eq!(weeks_in_year(2019), 52); +/// assert_eq!(weeks_in_year(2020), 53); +/// ``` +pub const fn weeks_in_year(year: i32) -> u8 { + match year.rem_euclid(400) { + 4 | 9 | 15 | 20 | 26 | 32 | 37 | 43 | 48 | 54 | 60 | 65 | 71 | 76 | 82 | 88 | 93 | 99 + | 105 | 111 | 116 | 122 | 128 | 133 | 139 | 144 | 150 | 156 | 161 | 167 | 172 | 178 + | 184 | 189 | 195 | 201 | 207 | 212 | 218 | 224 | 229 | 235 | 240 | 246 | 252 | 257 + | 263 | 268 | 274 | 280 | 285 | 291 | 296 | 303 | 308 | 314 | 320 | 325 | 331 | 336 + | 342 | 348 | 353 | 359 | 364 | 370 | 376 | 381 | 387 | 392 | 398 => 53, + _ => 52, + } +} diff --git a/third_party/rust/time-macros/.cargo-checksum.json b/third_party/rust/time-macros/.cargo-checksum.json new file mode 100644 index 0000000000..98cc1d6441 --- /dev/null +++ b/third_party/rust/time-macros/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"eb16c06efbfbf2ff5f48260785d4ecefbae6873d9d55c0ba2d388c6762e69b1f","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/date.rs":"ffcd3d0998ec67abb43a3f8eccc6104172f5061b855312b89d53bb82fece2460","src/datetime.rs":"5c7f6e07dc2f0dcfcd86216664df53bc008dbc86f346df57a9ff57f52fe43bc6","src/error.rs":"b597f98f425f1628b93ffea19f5f32163aa204e4cd25351bc114853a798e14b0","src/format_description/component.rs":"a05e7549db9bab4f3836f5fd5af18cacbfa6b323d0106b027e21bf438a5885e5","src/format_description/error.rs":"41253d7a02e14597915cf588811a272a90d1ce0f857f7769914e076dd5a66774","src/format_description/mod.rs":"da47af329408e9428753ad98ce433eaf026cfdd6e73e3142b23285251d32d0dd","src/format_description/modifier.rs":"c252c8a7d6608b594a6f715210ff67e804ae2f308025f62c8dd99d707627e4a9","src/format_description/parse.rs":"d65d6e7008030414ce6a860ff37c462c07ed89176a3f1462eeb46468a38fce7e","src/helpers/mod.rs":"54ce8e93512e18ef8761687eaac898a8227852a732f92aa5e80c28e23315bd0c","src/helpers/string.rs":"ba5699a4df344cbd71c4143f642f6bc07591f53978a9800d4b49ca1f461f87d9","src/lib.rs":"f99bded51bb861be5d708a3f756407f5b936a5febb719760c253a15113687e0d","src/offset.rs":"fc9341648e091b4d8f7bec47006c01c21cb038c7ef98bd36a492cf78e7533023","src/quote.rs":"b40251b0ca68e2362aff4297b87a027e48053f1a419113d3d0f7fe089a845a9c","src/serde_format_description.rs":"aa279c8005005fc87c52fa5e8be8ef8fc13ef456a18e3cd5d702ae81194ba4d9","src/time.rs":"3c06562358aed7ef624319c96e3f9c150a069606ab930de98ac379ef16b08100","src/to_tokens.rs":"825150a92396a019fee44f21da0bd257349e276d5e75a23ff86cfc625bef6f10"},"package":"d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"}
\ No newline at end of file diff --git a/third_party/rust/time-macros/Cargo.toml b/third_party/rust/time-macros/Cargo.toml new file mode 100644 index 0000000000..c770e23ad4 --- /dev/null +++ b/third_party/rust/time-macros/Cargo.toml @@ -0,0 +1,45 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.60.0" +name = "time-macros" +version = "0.2.6" +authors = [ + "Jacob Pratt <open-source@jhpratt.dev>", + "Time contributors", +] +description = """ + Procedural macros for the time crate. + This crate is an implementation detail and should not be relied upon directly. +""" +keywords = [ + "date", + "time", + "calendar", + "duration", +] +categories = ["date-and-time"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/time-rs/time" + +[lib] +proc-macro = true + +[dependencies.time-core] +version = "=0.1.0" + +[features] +formatting = [] +large-dates = [] +parsing = [] +serde = [] diff --git a/third_party/rust/time-macros/LICENSE-Apache b/third_party/rust/time-macros/LICENSE-Apache new file mode 100644 index 0000000000..7646f21e37 --- /dev/null +++ b/third_party/rust/time-macros/LICENSE-Apache @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Jacob Pratt et al. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/rust/time-macros/LICENSE-MIT b/third_party/rust/time-macros/LICENSE-MIT new file mode 100644 index 0000000000..a11a755732 --- /dev/null +++ b/third_party/rust/time-macros/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2022 Jacob Pratt et al. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/rust/time-macros/src/date.rs b/third_party/rust/time-macros/src/date.rs new file mode 100644 index 0000000000..574ef8ce6f --- /dev/null +++ b/third_party/rust/time-macros/src/date.rs @@ -0,0 +1,137 @@ +use std::iter::Peekable; + +use proc_macro::{token_stream, TokenTree}; +use time_core::util::{days_in_year, weeks_in_year}; + +use crate::helpers::{ + consume_any_ident, consume_number, consume_punct, days_in_year_month, ymd_to_yo, ywd_to_yo, +}; +use crate::to_tokens::ToTokenTree; +use crate::Error; + +#[cfg(feature = "large-dates")] +const MAX_YEAR: i32 = 999_999; +#[cfg(not(feature = "large-dates"))] +const MAX_YEAR: i32 = 9_999; + +pub(crate) struct Date { + pub(crate) year: i32, + pub(crate) ordinal: u16, +} + +pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Date, Error> { + let (year_sign_span, year_sign, explicit_sign) = if let Ok(span) = consume_punct('-', chars) { + (Some(span), -1, true) + } else if let Ok(span) = consume_punct('+', chars) { + (Some(span), 1, true) + } else { + (None, 1, false) + }; + let (year_span, mut year) = consume_number::<i32>("year", chars)?; + year *= year_sign; + if year.abs() > MAX_YEAR { + return Err(Error::InvalidComponent { + name: "year", + value: year.to_string(), + span_start: Some(year_sign_span.unwrap_or(year_span)), + span_end: Some(year_span), + }); + } + if !explicit_sign && year.abs() >= 10_000 { + return Err(Error::Custom { + message: "years with more than four digits must have an explicit sign".into(), + span_start: Some(year_sign_span.unwrap_or(year_span)), + span_end: Some(year_span), + }); + } + + consume_punct('-', chars)?; + + // year-week-day + if let Ok(w_span) = consume_any_ident(&["W"], chars) { + let (week_span, week) = consume_number::<u8>("week", chars)?; + consume_punct('-', chars)?; + let (day_span, day) = consume_number::<u8>("day", chars)?; + + if week > weeks_in_year(year) { + return Err(Error::InvalidComponent { + name: "week", + value: week.to_string(), + span_start: Some(w_span), + span_end: Some(week_span), + }); + } + if day == 0 || day > 7 { + return Err(Error::InvalidComponent { + name: "day", + value: day.to_string(), + span_start: Some(day_span), + span_end: Some(day_span), + }); + } + + let (year, ordinal) = ywd_to_yo(year, week, day); + + return Ok(Date { year, ordinal }); + } + + // We don't yet know whether it's year-month-day or year-ordinal. + let (month_or_ordinal_span, month_or_ordinal) = + consume_number::<u16>("month or ordinal", chars)?; + + // year-month-day + #[allow(clippy::branches_sharing_code)] // clarity + if consume_punct('-', chars).is_ok() { + let (month_span, month) = (month_or_ordinal_span, month_or_ordinal); + let (day_span, day) = consume_number::<u8>("day", chars)?; + + if month == 0 || month > 12 { + return Err(Error::InvalidComponent { + name: "month", + value: month.to_string(), + span_start: Some(month_span), + span_end: Some(month_span), + }); + } + let month = month as _; + if day == 0 || day > days_in_year_month(year, month) { + return Err(Error::InvalidComponent { + name: "day", + value: day.to_string(), + span_start: Some(day_span), + span_end: Some(day_span), + }); + } + + let (year, ordinal) = ymd_to_yo(year, month, day); + + Ok(Date { year, ordinal }) + } + // year-ordinal + else { + let (ordinal_span, ordinal) = (month_or_ordinal_span, month_or_ordinal); + + if ordinal == 0 || ordinal > days_in_year(year) { + return Err(Error::InvalidComponent { + name: "ordinal", + value: ordinal.to_string(), + span_start: Some(ordinal_span), + span_end: Some(ordinal_span), + }); + } + + Ok(Date { year, ordinal }) + } +} + +impl ToTokenTree for Date { + fn into_token_tree(self) -> TokenTree { + quote_group! {{ + const DATE: ::time::Date = ::time::Date::__from_ordinal_date_unchecked( + #(self.year), + #(self.ordinal), + ); + DATE + }} + } +} diff --git a/third_party/rust/time-macros/src/datetime.rs b/third_party/rust/time-macros/src/datetime.rs new file mode 100644 index 0000000000..2d41e9a532 --- /dev/null +++ b/third_party/rust/time-macros/src/datetime.rs @@ -0,0 +1,57 @@ +use std::iter::Peekable; + +use proc_macro::{token_stream, Ident, Span, TokenTree}; + +use crate::date::Date; +use crate::error::Error; +use crate::offset::Offset; +use crate::time::Time; +use crate::to_tokens::ToTokenTree; +use crate::{date, offset, time}; + +pub(crate) struct DateTime { + date: Date, + time: Time, + offset: Option<Offset>, +} + +pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<DateTime, Error> { + let date = date::parse(chars)?; + let time = time::parse(chars)?; + let offset = match offset::parse(chars) { + Ok(offset) => Some(offset), + Err(Error::UnexpectedEndOfInput | Error::MissingComponent { name: "sign", .. }) => None, + Err(err) => return Err(err), + }; + + if let Some(token) = chars.peek() { + return Err(Error::UnexpectedToken { + tree: token.clone(), + }); + } + + Ok(DateTime { date, time, offset }) +} + +impl ToTokenTree for DateTime { + fn into_token_tree(self) -> TokenTree { + let (type_name, maybe_offset) = match self.offset { + Some(offset) => ( + Ident::new("OffsetDateTime", Span::mixed_site()), + quote!(.assume_offset(#(offset))), + ), + None => ( + Ident::new("PrimitiveDateTime", Span::mixed_site()), + quote!(), + ), + }; + + quote_group! {{ + const DATE_TIME: ::time::#(type_name) = ::time::PrimitiveDateTime::new( + #(self.date), + #(self.time), + ) #S(maybe_offset); + DATE_TIME + }} + } +} diff --git a/third_party/rust/time-macros/src/error.rs b/third_party/rust/time-macros/src/error.rs new file mode 100644 index 0000000000..4de369dafc --- /dev/null +++ b/third_party/rust/time-macros/src/error.rs @@ -0,0 +1,136 @@ +use std::borrow::Cow; +use std::fmt; + +use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; + +#[cfg(any(feature = "formatting", feature = "parsing"))] +use crate::format_description::error::InvalidFormatDescription; + +trait WithSpan { + fn with_span(self, span: Span) -> Self; +} + +impl WithSpan for TokenTree { + fn with_span(mut self, span: Span) -> Self { + self.set_span(span); + self + } +} + +pub(crate) enum Error { + MissingComponent { + name: &'static str, + span_start: Option<Span>, + span_end: Option<Span>, + }, + InvalidComponent { + name: &'static str, + value: String, + span_start: Option<Span>, + span_end: Option<Span>, + }, + #[cfg(any(feature = "formatting", feature = "parsing"))] + ExpectedString { + span_start: Option<Span>, + span_end: Option<Span>, + }, + UnexpectedToken { + tree: TokenTree, + }, + UnexpectedEndOfInput, + #[cfg(any(feature = "formatting", feature = "parsing"))] + InvalidFormatDescription { + error: InvalidFormatDescription, + span_start: Option<Span>, + span_end: Option<Span>, + }, + Custom { + message: Cow<'static, str>, + span_start: Option<Span>, + span_end: Option<Span>, + }, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MissingComponent { name, .. } => write!(f, "missing component: {name}"), + Self::InvalidComponent { name, value, .. } => { + write!(f, "invalid component: {name} was {value}") + } + #[cfg(any(feature = "formatting", feature = "parsing"))] + Self::ExpectedString { .. } => f.write_str("expected string"), + Self::UnexpectedToken { tree } => write!(f, "unexpected token: {tree}"), + Self::UnexpectedEndOfInput => f.write_str("unexpected end of input"), + #[cfg(any(feature = "formatting", feature = "parsing"))] + Self::InvalidFormatDescription { error, .. } => error.fmt(f), + Self::Custom { message, .. } => f.write_str(message), + } + } +} + +impl Error { + fn span_start(&self) -> Span { + match self { + Self::MissingComponent { span_start, .. } + | Self::InvalidComponent { span_start, .. } + | Self::Custom { span_start, .. } => *span_start, + #[cfg(any(feature = "formatting", feature = "parsing"))] + Self::ExpectedString { span_start, .. } + | Self::InvalidFormatDescription { span_start, .. } => *span_start, + Self::UnexpectedToken { tree } => Some(tree.span()), + Self::UnexpectedEndOfInput => Some(Span::mixed_site()), + } + .unwrap_or_else(Span::mixed_site) + } + + fn span_end(&self) -> Span { + match self { + Self::MissingComponent { span_end, .. } + | Self::InvalidComponent { span_end, .. } + | Self::Custom { span_end, .. } => *span_end, + #[cfg(any(feature = "formatting", feature = "parsing"))] + Self::ExpectedString { span_end, .. } + | Self::InvalidFormatDescription { span_end, .. } => *span_end, + Self::UnexpectedToken { tree, .. } => Some(tree.span()), + Self::UnexpectedEndOfInput => Some(Span::mixed_site()), + } + .unwrap_or_else(|| self.span_start()) + } + + pub(crate) fn to_compile_error(&self) -> TokenStream { + let (start, end) = (self.span_start(), self.span_end()); + + [ + TokenTree::from(Punct::new(':', Spacing::Joint)).with_span(start), + TokenTree::from(Punct::new(':', Spacing::Alone)).with_span(start), + TokenTree::from(Ident::new("core", start)), + TokenTree::from(Punct::new(':', Spacing::Joint)).with_span(start), + TokenTree::from(Punct::new(':', Spacing::Alone)).with_span(start), + TokenTree::from(Ident::new("compile_error", start)), + TokenTree::from(Punct::new('!', Spacing::Alone)).with_span(start), + TokenTree::from(Group::new( + Delimiter::Parenthesis, + TokenStream::from( + TokenTree::from(Literal::string(&self.to_string())).with_span(end), + ), + )) + .with_span(end), + ] + .iter() + .cloned() + .collect() + } + + /// Like `to_compile_error`, but for use in macros that produce items. + #[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))] + pub(crate) fn to_compile_error_standalone(&self) -> TokenStream { + let end = self.span_end(); + self.to_compile_error() + .into_iter() + .chain(std::iter::once( + TokenTree::from(Punct::new(';', Spacing::Alone)).with_span(end), + )) + .collect() + } +} diff --git a/third_party/rust/time-macros/src/format_description/component.rs b/third_party/rust/time-macros/src/format_description/component.rs new file mode 100644 index 0000000000..850da91d20 --- /dev/null +++ b/third_party/rust/time-macros/src/format_description/component.rs @@ -0,0 +1,168 @@ +use proc_macro::{Ident, Span, TokenStream}; + +use crate::format_description::error::InvalidFormatDescription; +use crate::format_description::modifier; +use crate::format_description::modifier::Modifiers; +use crate::to_tokens::ToTokenStream; + +pub(crate) enum Component { + Day(modifier::Day), + Month(modifier::Month), + Ordinal(modifier::Ordinal), + Weekday(modifier::Weekday), + WeekNumber(modifier::WeekNumber), + Year(modifier::Year), + Hour(modifier::Hour), + Minute(modifier::Minute), + Period(modifier::Period), + Second(modifier::Second), + Subsecond(modifier::Subsecond), + OffsetHour(modifier::OffsetHour), + OffsetMinute(modifier::OffsetMinute), + OffsetSecond(modifier::OffsetSecond), +} + +impl ToTokenStream for Component { + fn append_to(self, ts: &mut TokenStream) { + let mut mts = TokenStream::new(); + + macro_rules! component_name_and_append { + ($($name:ident)*) => { + match self { + $(Self::$name(modifier) => { + modifier.append_to(&mut mts); + stringify!($name) + })* + } + }; + } + + let component = component_name_and_append![ + Day + Month + Ordinal + Weekday + WeekNumber + Year + Hour + Minute + Period + Second + Subsecond + OffsetHour + OffsetMinute + OffsetSecond + ]; + let component = Ident::new(component, Span::mixed_site()); + + quote_append! { ts + ::time::format_description::Component::#(component)(#S(mts)) + } + } +} + +pub(crate) enum NakedComponent { + Day, + Month, + Ordinal, + Weekday, + WeekNumber, + Year, + Hour, + Minute, + Period, + Second, + Subsecond, + OffsetHour, + OffsetMinute, + OffsetSecond, +} + +impl NakedComponent { + pub(crate) fn parse( + component_name: &[u8], + component_index: usize, + ) -> Result<Self, InvalidFormatDescription> { + match component_name { + b"day" => Ok(Self::Day), + b"month" => Ok(Self::Month), + b"ordinal" => Ok(Self::Ordinal), + b"weekday" => Ok(Self::Weekday), + b"week_number" => Ok(Self::WeekNumber), + b"year" => Ok(Self::Year), + b"hour" => Ok(Self::Hour), + b"minute" => Ok(Self::Minute), + b"period" => Ok(Self::Period), + b"second" => Ok(Self::Second), + b"subsecond" => Ok(Self::Subsecond), + b"offset_hour" => Ok(Self::OffsetHour), + b"offset_minute" => Ok(Self::OffsetMinute), + b"offset_second" => Ok(Self::OffsetSecond), + b"" => Err(InvalidFormatDescription::MissingComponentName { + index: component_index, + }), + _ => Err(InvalidFormatDescription::InvalidComponentName { + name: String::from_utf8_lossy(component_name).into_owned(), + index: component_index, + }), + } + } + + pub(crate) fn attach_modifiers(self, modifiers: Modifiers) -> Component { + match self { + Self::Day => Component::Day(modifier::Day { + padding: modifiers.padding.unwrap_or_default(), + }), + Self::Month => Component::Month(modifier::Month { + padding: modifiers.padding.unwrap_or_default(), + repr: modifiers.month_repr.unwrap_or_default(), + case_sensitive: modifiers.case_sensitive.unwrap_or(true), + }), + Self::Ordinal => Component::Ordinal(modifier::Ordinal { + padding: modifiers.padding.unwrap_or_default(), + }), + Self::Weekday => Component::Weekday(modifier::Weekday { + repr: modifiers.weekday_repr.unwrap_or_default(), + one_indexed: modifiers.weekday_is_one_indexed.unwrap_or(true), + case_sensitive: modifiers.case_sensitive.unwrap_or(true), + }), + Self::WeekNumber => Component::WeekNumber(modifier::WeekNumber { + padding: modifiers.padding.unwrap_or_default(), + repr: modifiers.week_number_repr.unwrap_or_default(), + }), + Self::Year => Component::Year(modifier::Year { + padding: modifiers.padding.unwrap_or_default(), + repr: modifiers.year_repr.unwrap_or_default(), + iso_week_based: modifiers.year_is_iso_week_based.unwrap_or_default(), + sign_is_mandatory: modifiers.sign_is_mandatory.unwrap_or_default(), + }), + Self::Hour => Component::Hour(modifier::Hour { + padding: modifiers.padding.unwrap_or_default(), + is_12_hour_clock: modifiers.hour_is_12_hour_clock.unwrap_or_default(), + }), + Self::Minute => Component::Minute(modifier::Minute { + padding: modifiers.padding.unwrap_or_default(), + }), + Self::Period => Component::Period(modifier::Period { + is_uppercase: modifiers.period_is_uppercase.unwrap_or(true), + case_sensitive: modifiers.case_sensitive.unwrap_or(true), + }), + Self::Second => Component::Second(modifier::Second { + padding: modifiers.padding.unwrap_or_default(), + }), + Self::Subsecond => Component::Subsecond(modifier::Subsecond { + digits: modifiers.subsecond_digits.unwrap_or_default(), + }), + Self::OffsetHour => Component::OffsetHour(modifier::OffsetHour { + sign_is_mandatory: modifiers.sign_is_mandatory.unwrap_or_default(), + padding: modifiers.padding.unwrap_or_default(), + }), + Self::OffsetMinute => Component::OffsetMinute(modifier::OffsetMinute { + padding: modifiers.padding.unwrap_or_default(), + }), + Self::OffsetSecond => Component::OffsetSecond(modifier::OffsetSecond { + padding: modifiers.padding.unwrap_or_default(), + }), + } + } +} diff --git a/third_party/rust/time-macros/src/format_description/error.rs b/third_party/rust/time-macros/src/format_description/error.rs new file mode 100644 index 0000000000..9aacd7dc9e --- /dev/null +++ b/third_party/rust/time-macros/src/format_description/error.rs @@ -0,0 +1,29 @@ +use std::fmt; + +pub(crate) enum InvalidFormatDescription { + UnclosedOpeningBracket { index: usize }, + InvalidComponentName { name: String, index: usize }, + InvalidModifier { value: String, index: usize }, + MissingComponentName { index: usize }, +} + +impl fmt::Display for InvalidFormatDescription { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[allow(clippy::enum_glob_use)] + use InvalidFormatDescription::*; + match self { + UnclosedOpeningBracket { index } => { + write!(f, "unclosed opening bracket at byte index {index}") + } + InvalidComponentName { name, index } => { + write!(f, "invalid component name `{name}` at byte index {index}",) + } + InvalidModifier { value, index } => { + write!(f, "invalid modifier `{value}` at byte index {index}") + } + MissingComponentName { index } => { + write!(f, "missing component name at byte index {index}") + } + } + } +} diff --git a/third_party/rust/time-macros/src/format_description/mod.rs b/third_party/rust/time-macros/src/format_description/mod.rs new file mode 100644 index 0000000000..dd32db74d5 --- /dev/null +++ b/third_party/rust/time-macros/src/format_description/mod.rs @@ -0,0 +1,40 @@ +mod component; +pub(crate) mod error; +pub(crate) mod modifier; +pub(crate) mod parse; + +use proc_macro::{Literal, TokenStream}; + +pub(crate) use self::component::Component; +pub(crate) use self::parse::parse; +use crate::to_tokens::ToTokenStream; + +mod helper { + #[must_use = "This does not modify the original slice."] + pub(crate) fn consume_whitespace<'a>(bytes: &'a [u8], index: &mut usize) -> &'a [u8] { + let first_non_whitespace = bytes + .iter() + .position(|c| !c.is_ascii_whitespace()) + .unwrap_or(bytes.len()); + *index += first_non_whitespace; + &bytes[first_non_whitespace..] + } +} + +#[allow(single_use_lifetimes)] // false positive +#[allow(variant_size_differences)] +pub(crate) enum FormatItem<'a> { + Literal(&'a [u8]), + Component(Component), +} + +impl ToTokenStream for FormatItem<'_> { + fn append_to(self, ts: &mut TokenStream) { + quote_append! { ts + ::time::format_description::FormatItem::#S(match self { + FormatItem::Literal(bytes) => quote! { Literal(#(Literal::byte_string(bytes))) }, + FormatItem::Component(component) => quote! { Component(#S(component)) }, + }) + } + } +} diff --git a/third_party/rust/time-macros/src/format_description/modifier.rs b/third_party/rust/time-macros/src/format_description/modifier.rs new file mode 100644 index 0000000000..f4e641a7b9 --- /dev/null +++ b/third_party/rust/time-macros/src/format_description/modifier.rs @@ -0,0 +1,417 @@ +use core::mem; + +use proc_macro::{Ident, Span, TokenStream, TokenTree}; + +use crate::format_description::error::InvalidFormatDescription; +use crate::format_description::helper; +use crate::to_tokens::{ToTokenStream, ToTokenTree}; + +macro_rules! to_tokens { + ( + $(#[$struct_attr:meta])* + $struct_vis:vis struct $struct_name:ident {$( + $(#[$field_attr:meta])* + $field_vis:vis $field_name:ident : $field_ty:ty + ),+ $(,)?} + ) => { + $(#[$struct_attr])* + $struct_vis struct $struct_name {$( + $(#[$field_attr])* + $field_vis $field_name: $field_ty + ),+} + + impl ToTokenTree for $struct_name { + fn into_token_tree(self) -> TokenTree { + let mut tokens = TokenStream::new(); + let Self {$($field_name),+} = self; + + quote_append! { tokens + let mut value = ::time::format_description::modifier::$struct_name::default(); + }; + $( + quote_append!(tokens value.$field_name =); + $field_name.append_to(&mut tokens); + quote_append!(tokens ;); + )+ + quote_append!(tokens value); + + proc_macro::TokenTree::Group(proc_macro::Group::new( + proc_macro::Delimiter::Brace, + tokens, + )) + } + } + }; + + ( + $(#[$enum_attr:meta])* + $enum_vis:vis enum $enum_name:ident {$( + $(#[$variant_attr:meta])* + $variant_name:ident + ),+ $(,)?} + ) => { + $(#[$enum_attr])* + $enum_vis enum $enum_name {$( + $(#[$variant_attr])* + $variant_name + ),+} + + impl ToTokenStream for $enum_name { + fn append_to(self, ts: &mut TokenStream) { + quote_append! { ts + ::time::format_description::modifier::$enum_name:: + }; + let name = match self { + $(Self::$variant_name => stringify!($variant_name)),+ + }; + ts.extend([TokenTree::Ident(Ident::new(name, Span::mixed_site()))]); + } + } + } +} + +to_tokens! { + pub(crate) struct Day { + pub(crate) padding: Padding, + } +} + +to_tokens! { + pub(crate) enum MonthRepr { + Numerical, + Long, + Short, + } +} + +to_tokens! { + pub(crate) struct Month { + pub(crate) padding: Padding, + pub(crate) repr: MonthRepr, + pub(crate) case_sensitive: bool, + } +} + +to_tokens! { + pub(crate) struct Ordinal { + pub(crate) padding: Padding, + } +} + +to_tokens! { + pub(crate) enum WeekdayRepr { + Short, + Long, + Sunday, + Monday, + } +} + +to_tokens! { + pub(crate) struct Weekday { + pub(crate) repr: WeekdayRepr, + pub(crate) one_indexed: bool, + pub(crate) case_sensitive: bool, + } +} + +to_tokens! { + pub(crate) enum WeekNumberRepr { + Iso, + Sunday, + Monday, + } +} + +to_tokens! { + pub(crate) struct WeekNumber { + pub(crate) padding: Padding, + pub(crate) repr: WeekNumberRepr, + } +} + +to_tokens! { + pub(crate) enum YearRepr { + Full, + LastTwo, + } +} + +to_tokens! { + pub(crate) struct Year { + pub(crate) padding: Padding, + pub(crate) repr: YearRepr, + pub(crate) iso_week_based: bool, + pub(crate) sign_is_mandatory: bool, + } +} + +to_tokens! { + pub(crate) struct Hour { + pub(crate) padding: Padding, + pub(crate) is_12_hour_clock: bool, + } +} + +to_tokens! { + pub(crate) struct Minute { + pub(crate) padding: Padding, + } +} + +to_tokens! { + pub(crate) struct Period { + pub(crate) is_uppercase: bool, + pub(crate) case_sensitive: bool, + } +} + +to_tokens! { + pub(crate) struct Second { + pub(crate) padding: Padding, + } +} + +to_tokens! { + pub(crate) enum SubsecondDigits { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + OneOrMore, + } +} + +to_tokens! { + pub(crate) struct Subsecond { + pub(crate) digits: SubsecondDigits, + } +} + +to_tokens! { + pub(crate) struct OffsetHour { + pub(crate) sign_is_mandatory: bool, + pub(crate) padding: Padding, + } +} + +to_tokens! { + pub(crate) struct OffsetMinute { + pub(crate) padding: Padding, + } +} + +to_tokens! { + pub(crate) struct OffsetSecond { + pub(crate) padding: Padding, + } +} + +to_tokens! { + pub(crate) enum Padding { + Space, + Zero, + None, + } +} + +macro_rules! impl_default { + ($($type:ty => $default:expr;)*) => {$( + impl Default for $type { + fn default() -> Self { + $default + } + } + )*}; +} + +impl_default! { + Day => Self { padding: Padding::default() }; + MonthRepr => Self::Numerical; + Month => Self { + padding: Padding::default(), + repr: MonthRepr::default(), + case_sensitive: true, + }; + Ordinal => Self { padding: Padding::default() }; + WeekdayRepr => Self::Long; + Weekday => Self { + repr: WeekdayRepr::default(), + one_indexed: true, + case_sensitive: true, + }; + WeekNumberRepr => Self::Iso; + WeekNumber => Self { + padding: Padding::default(), + repr: WeekNumberRepr::default(), + }; + YearRepr => Self::Full; + Year => Self { + padding: Padding::default(), + repr: YearRepr::default(), + iso_week_based: false, + sign_is_mandatory: false, + }; + Hour => Self { + padding: Padding::default(), + is_12_hour_clock: false, + }; + Minute => Self { padding: Padding::default() }; + Period => Self { is_uppercase: true, case_sensitive: true }; + Second => Self { padding: Padding::default() }; + SubsecondDigits => Self::OneOrMore; + Subsecond => Self { digits: SubsecondDigits::default() }; + OffsetHour => Self { + sign_is_mandatory: true, + padding: Padding::default(), + }; + OffsetMinute => Self { padding: Padding::default() }; + OffsetSecond => Self { padding: Padding::default() }; + Padding => Self::Zero; +} + +#[derive(Default)] +pub(crate) struct Modifiers { + pub(crate) padding: Option<Padding>, + pub(crate) hour_is_12_hour_clock: Option<bool>, + pub(crate) period_is_uppercase: Option<bool>, + pub(crate) month_repr: Option<MonthRepr>, + pub(crate) subsecond_digits: Option<SubsecondDigits>, + pub(crate) weekday_repr: Option<WeekdayRepr>, + pub(crate) weekday_is_one_indexed: Option<bool>, + pub(crate) week_number_repr: Option<WeekNumberRepr>, + pub(crate) year_repr: Option<YearRepr>, + pub(crate) year_is_iso_week_based: Option<bool>, + pub(crate) sign_is_mandatory: Option<bool>, + pub(crate) case_sensitive: Option<bool>, +} + +impl Modifiers { + #[allow(clippy::too_many_lines)] + pub(crate) fn parse( + component_name: &[u8], + mut bytes: &[u8], + index: &mut usize, + ) -> Result<Self, InvalidFormatDescription> { + let mut modifiers = Self::default(); + + while !bytes.is_empty() { + // Trim any whitespace between modifiers. + bytes = helper::consume_whitespace(bytes, index); + + let modifier; + if let Some(whitespace_loc) = bytes.iter().position(u8::is_ascii_whitespace) { + *index += whitespace_loc; + modifier = &bytes[..whitespace_loc]; + bytes = &bytes[whitespace_loc..]; + } else { + modifier = mem::take(&mut bytes); + } + + if modifier.is_empty() { + break; + } + + match (component_name, modifier) { + ( + b"day" | b"hour" | b"minute" | b"month" | b"offset_hour" | b"offset_minute" + | b"offset_second" | b"ordinal" | b"second" | b"week_number" | b"year", + b"padding:space", + ) => modifiers.padding = Some(Padding::Space), + ( + b"day" | b"hour" | b"minute" | b"month" | b"offset_hour" | b"offset_minute" + | b"offset_second" | b"ordinal" | b"second" | b"week_number" | b"year", + b"padding:zero", + ) => modifiers.padding = Some(Padding::Zero), + ( + b"day" | b"hour" | b"minute" | b"month" | b"offset_hour" | b"offset_minute" + | b"offset_second" | b"ordinal" | b"second" | b"week_number" | b"year", + b"padding:none", + ) => modifiers.padding = Some(Padding::None), + (b"hour", b"repr:24") => modifiers.hour_is_12_hour_clock = Some(false), + (b"hour", b"repr:12") => modifiers.hour_is_12_hour_clock = Some(true), + (b"month" | b"period" | b"weekday", b"case_sensitive:true") => { + modifiers.case_sensitive = Some(true) + } + (b"month" | b"period" | b"weekday", b"case_sensitive:false") => { + modifiers.case_sensitive = Some(false) + } + (b"month", b"repr:numerical") => modifiers.month_repr = Some(MonthRepr::Numerical), + (b"month", b"repr:long") => modifiers.month_repr = Some(MonthRepr::Long), + (b"month", b"repr:short") => modifiers.month_repr = Some(MonthRepr::Short), + (b"offset_hour" | b"year", b"sign:automatic") => { + modifiers.sign_is_mandatory = Some(false); + } + (b"offset_hour" | b"year", b"sign:mandatory") => { + modifiers.sign_is_mandatory = Some(true); + } + (b"period", b"case:upper") => modifiers.period_is_uppercase = Some(true), + (b"period", b"case:lower") => modifiers.period_is_uppercase = Some(false), + (b"subsecond", b"digits:1") => { + modifiers.subsecond_digits = Some(SubsecondDigits::One); + } + (b"subsecond", b"digits:2") => { + modifiers.subsecond_digits = Some(SubsecondDigits::Two); + } + (b"subsecond", b"digits:3") => { + modifiers.subsecond_digits = Some(SubsecondDigits::Three); + } + (b"subsecond", b"digits:4") => { + modifiers.subsecond_digits = Some(SubsecondDigits::Four); + } + (b"subsecond", b"digits:5") => { + modifiers.subsecond_digits = Some(SubsecondDigits::Five); + } + (b"subsecond", b"digits:6") => { + modifiers.subsecond_digits = Some(SubsecondDigits::Six); + } + (b"subsecond", b"digits:7") => { + modifiers.subsecond_digits = Some(SubsecondDigits::Seven); + } + (b"subsecond", b"digits:8") => { + modifiers.subsecond_digits = Some(SubsecondDigits::Eight); + } + (b"subsecond", b"digits:9") => { + modifiers.subsecond_digits = Some(SubsecondDigits::Nine); + } + (b"subsecond", b"digits:1+") => { + modifiers.subsecond_digits = Some(SubsecondDigits::OneOrMore); + } + (b"weekday", b"repr:short") => modifiers.weekday_repr = Some(WeekdayRepr::Short), + (b"weekday", b"repr:long") => modifiers.weekday_repr = Some(WeekdayRepr::Long), + (b"weekday", b"repr:sunday") => modifiers.weekday_repr = Some(WeekdayRepr::Sunday), + (b"weekday", b"repr:monday") => modifiers.weekday_repr = Some(WeekdayRepr::Monday), + (b"weekday", b"one_indexed:true") => modifiers.weekday_is_one_indexed = Some(true), + (b"weekday", b"one_indexed:false") => { + modifiers.weekday_is_one_indexed = Some(false); + } + (b"week_number", b"repr:iso") => { + modifiers.week_number_repr = Some(WeekNumberRepr::Iso); + } + (b"week_number", b"repr:sunday") => { + modifiers.week_number_repr = Some(WeekNumberRepr::Sunday); + } + (b"week_number", b"repr:monday") => { + modifiers.week_number_repr = Some(WeekNumberRepr::Monday); + } + (b"year", b"repr:full") => modifiers.year_repr = Some(YearRepr::Full), + (b"year", b"repr:last_two") => modifiers.year_repr = Some(YearRepr::LastTwo), + (b"year", b"base:calendar") => modifiers.year_is_iso_week_based = Some(false), + (b"year", b"base:iso_week") => modifiers.year_is_iso_week_based = Some(true), + _ => { + return Err(InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(modifier).into_owned(), + index: *index, + }); + } + } + } + + Ok(modifiers) + } +} diff --git a/third_party/rust/time-macros/src/format_description/parse.rs b/third_party/rust/time-macros/src/format_description/parse.rs new file mode 100644 index 0000000000..19c7bf6080 --- /dev/null +++ b/third_party/rust/time-macros/src/format_description/parse.rs @@ -0,0 +1,84 @@ +use proc_macro::Span; + +use crate::format_description::component::{Component, NakedComponent}; +use crate::format_description::error::InvalidFormatDescription; +use crate::format_description::{helper, modifier, FormatItem}; +use crate::Error; + +struct ParsedItem<'a> { + item: FormatItem<'a>, + remaining: &'a [u8], +} + +fn parse_component(mut s: &[u8], index: &mut usize) -> Result<Component, InvalidFormatDescription> { + s = helper::consume_whitespace(s, index); + + let component_index = *index; + let whitespace_loc = s + .iter() + .position(u8::is_ascii_whitespace) + .unwrap_or(s.len()); + *index += whitespace_loc; + let component_name = &s[..whitespace_loc]; + s = &s[whitespace_loc..]; + s = helper::consume_whitespace(s, index); + + Ok(NakedComponent::parse(component_name, component_index)? + .attach_modifiers(modifier::Modifiers::parse(component_name, s, index)?)) +} + +fn parse_literal<'a>(s: &'a [u8], index: &mut usize) -> ParsedItem<'a> { + let loc = s.iter().position(|&c| c == b'[').unwrap_or(s.len()); + *index += loc; + ParsedItem { + item: FormatItem::Literal(&s[..loc]), + remaining: &s[loc..], + } +} + +fn parse_item<'a>( + s: &'a [u8], + index: &mut usize, +) -> Result<ParsedItem<'a>, InvalidFormatDescription> { + if let [b'[', b'[', remaining @ ..] = s { + *index += 2; + return Ok(ParsedItem { + item: FormatItem::Literal(b"["), + remaining, + }); + }; + + if s.starts_with(b"[") { + if let Some(bracket_index) = s.iter().position(|&c| c == b']') { + *index += 1; // opening bracket + let ret_val = ParsedItem { + item: FormatItem::Component(parse_component(&s[1..bracket_index], index)?), + remaining: &s[bracket_index + 1..], + }; + *index += 1; // closing bracket + Ok(ret_val) + } else { + Err(InvalidFormatDescription::UnclosedOpeningBracket { index: *index }) + } + } else { + Ok(parse_literal(s, index)) + } +} + +pub(crate) fn parse(mut s: &[u8], span: Span) -> Result<Vec<FormatItem<'_>>, Error> { + let mut compound = Vec::new(); + let mut loc = 0; + + while !s.is_empty() { + let ParsedItem { item, remaining } = + parse_item(s, &mut loc).map_err(|error| Error::InvalidFormatDescription { + error, + span_start: Some(span), + span_end: Some(span), + })?; + s = remaining; + compound.push(item); + } + + Ok(compound) +} diff --git a/third_party/rust/time-macros/src/helpers/mod.rs b/third_party/rust/time-macros/src/helpers/mod.rs new file mode 100644 index 0000000000..cbf3ba3ed5 --- /dev/null +++ b/third_party/rust/time-macros/src/helpers/mod.rs @@ -0,0 +1,129 @@ +#[cfg(any(feature = "formatting", feature = "parsing"))] +mod string; + +use std::iter::Peekable; +use std::str::FromStr; + +#[cfg(any(feature = "formatting", feature = "parsing"))] +use proc_macro::TokenStream; +use proc_macro::{token_stream, Span, TokenTree}; +use time_core::util::{days_in_year, is_leap_year}; + +use crate::Error; + +#[cfg(any(feature = "formatting", feature = "parsing"))] +pub(crate) fn get_string_literal(tokens: TokenStream) -> Result<(Span, Vec<u8>), Error> { + let mut tokens = tokens.into_iter(); + + match (tokens.next(), tokens.next()) { + (Some(TokenTree::Literal(literal)), None) => string::parse(&literal), + (Some(tree), None) => Err(Error::ExpectedString { + span_start: Some(tree.span()), + span_end: Some(tree.span()), + }), + (_, Some(tree)) => Err(Error::UnexpectedToken { tree }), + (None, None) => Err(Error::ExpectedString { + span_start: None, + span_end: None, + }), + } +} + +pub(crate) fn consume_number<T: FromStr>( + component_name: &'static str, + chars: &mut Peekable<token_stream::IntoIter>, +) -> Result<(Span, T), Error> { + let (span, digits) = match chars.next() { + Some(TokenTree::Literal(literal)) => (literal.span(), literal.to_string()), + Some(tree) => return Err(Error::UnexpectedToken { tree }), + None => return Err(Error::UnexpectedEndOfInput), + }; + + if let Ok(value) = digits.replace('_', "").parse() { + Ok((span, value)) + } else { + Err(Error::InvalidComponent { + name: component_name, + value: digits, + span_start: Some(span), + span_end: Some(span), + }) + } +} + +pub(crate) fn consume_any_ident( + idents: &[&str], + chars: &mut Peekable<token_stream::IntoIter>, +) -> Result<Span, Error> { + match chars.peek() { + Some(TokenTree::Ident(char)) if idents.contains(&char.to_string().as_str()) => { + let ret = Ok(char.span()); + drop(chars.next()); + ret + } + Some(tree) => Err(Error::UnexpectedToken { tree: tree.clone() }), + None => Err(Error::UnexpectedEndOfInput), + } +} + +pub(crate) fn consume_punct( + c: char, + chars: &mut Peekable<token_stream::IntoIter>, +) -> Result<Span, Error> { + match chars.peek() { + Some(TokenTree::Punct(punct)) if *punct == c => { + let ret = Ok(punct.span()); + drop(chars.next()); + ret + } + Some(tree) => Err(Error::UnexpectedToken { tree: tree.clone() }), + None => Err(Error::UnexpectedEndOfInput), + } +} + +fn jan_weekday(year: i32, ordinal: i32) -> u8 { + macro_rules! div_floor { + ($a:expr, $b:expr) => {{ + let (_quotient, _remainder) = ($a / $b, $a % $b); + if (_remainder > 0 && $b < 0) || (_remainder < 0 && $b > 0) { + _quotient - 1 + } else { + _quotient + } + }}; + } + + let adj_year = year - 1; + ((ordinal + adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100) + + div_floor!(adj_year, 400) + + 6) + .rem_euclid(7)) as _ +} + +pub(crate) fn days_in_year_month(year: i32, month: u8) -> u8 { + [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month as usize - 1] + + (month == 2 && is_leap_year(year)) as u8 +} + +pub(crate) fn ywd_to_yo(year: i32, week: u8, iso_weekday_number: u8) -> (i32, u16) { + let (ordinal, overflow) = (u16::from(week) * 7 + u16::from(iso_weekday_number)) + .overflowing_sub(u16::from(jan_weekday(year, 4)) + 4); + + if overflow || ordinal == 0 { + return (year - 1, (ordinal.wrapping_add(days_in_year(year - 1)))); + } + + let days_in_cur_year = days_in_year(year); + if ordinal > days_in_cur_year { + (year + 1, ordinal - days_in_cur_year) + } else { + (year, ordinal) + } +} + +pub(crate) fn ymd_to_yo(year: i32, month: u8, day: u8) -> (i32, u16) { + let ordinal = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334][month as usize - 1] + + (month > 2 && is_leap_year(year)) as u16; + + (year, ordinal + u16::from(day)) +} diff --git a/third_party/rust/time-macros/src/helpers/string.rs b/third_party/rust/time-macros/src/helpers/string.rs new file mode 100644 index 0000000000..fa3780f5e3 --- /dev/null +++ b/third_party/rust/time-macros/src/helpers/string.rs @@ -0,0 +1,188 @@ +use std::ops::{Index, RangeFrom}; + +use proc_macro::Span; + +use crate::Error; + +pub(crate) fn parse(token: &proc_macro::Literal) -> Result<(Span, Vec<u8>), Error> { + let span = token.span(); + let repr = token.to_string(); + + match repr.as_bytes() { + [b'"', ..] => Ok((span, parse_lit_str_cooked(&repr[1..]))), + [b'b', b'"', rest @ ..] => Ok((span, parse_lit_byte_str_cooked(rest))), + [b'r', rest @ ..] | [b'b', b'r', rest @ ..] => Ok((span, parse_lit_str_raw(rest))), + _ => Err(Error::ExpectedString { + span_start: Some(span), + span_end: Some(span), + }), + } +} + +fn byte(s: impl AsRef<[u8]>, idx: usize) -> u8 { + s.as_ref().get(idx).copied().unwrap_or_default() +} + +fn parse_lit_str_cooked(mut s: &str) -> Vec<u8> { + let mut content = String::new(); + 'outer: loop { + let ch = match byte(s, 0) { + b'"' => break, + b'\\' => { + let b = byte(s, 1); + s = &s[2..]; + match b { + b'x' => { + let (byte, rest) = backslash_x(s); + s = rest; + char::from_u32(u32::from(byte)).expect("byte was just validated") + } + b'u' => { + let (chr, rest) = backslash_u(s); + s = rest; + chr + } + b'n' => '\n', + b'r' => '\r', + b't' => '\t', + b'\\' => '\\', + b'0' => '\0', + b'\'' => '\'', + b'"' => '"', + b'\r' | b'\n' => loop { + let ch = s.chars().next().unwrap_or_default(); + if ch.is_whitespace() { + s = &s[ch.len_utf8()..]; + } else { + continue 'outer; + } + }, + _ => unreachable!("invalid escape"), + } + } + b'\r' => { + // bare CR not permitted + s = &s[2..]; + '\n' + } + _ => { + let ch = s.chars().next().unwrap_or_default(); + s = &s[ch.len_utf8()..]; + ch + } + }; + content.push(ch); + } + + content.into_bytes() +} + +fn parse_lit_str_raw(s: &[u8]) -> Vec<u8> { + let mut pounds = 0; + while byte(s, pounds) == b'#' { + pounds += 1; + } + let close = s + .iter() + .rposition(|&b| b == b'"') + .expect("had a string without trailing \""); + + s[pounds + 1..close].to_owned() +} + +fn parse_lit_byte_str_cooked(mut v: &[u8]) -> Vec<u8> { + let mut out = Vec::new(); + 'outer: loop { + let byte = match byte(v, 0) { + b'"' => break, + b'\\' => { + let b = byte(v, 1); + v = &v[2..]; + match b { + b'x' => { + let (byte, rest) = backslash_x(v); + v = rest; + byte + } + b'n' => b'\n', + b'r' => b'\r', + b't' => b'\t', + b'\\' => b'\\', + b'0' => b'\0', + b'\'' => b'\'', + b'"' => b'"', + b'\r' | b'\n' => loop { + let byte = byte(v, 0); + let ch = char::from_u32(u32::from(byte)).expect("invalid byte"); + if ch.is_whitespace() { + v = &v[1..]; + } else { + continue 'outer; + } + }, + _ => unreachable!("invalid escape"), + } + } + b'\r' => { + // bare CR not permitted + v = &v[2..]; + b'\n' + } + b => { + v = &v[1..]; + b + } + }; + out.push(byte); + } + + out +} + +fn backslash_x<S>(s: &S) -> (u8, &S) +where + S: Index<RangeFrom<usize>, Output = S> + AsRef<[u8]> + ?Sized, +{ + let mut ch = 0; + let b0 = byte(s, 0); + let b1 = byte(s, 1); + ch += 0x10 * (b0 - b'0'); + ch += match b1 { + b'0'..=b'9' => b1 - b'0', + b'a'..=b'f' => 10 + (b1 - b'a'), + b'A'..=b'F' => 10 + (b1 - b'A'), + _ => unreachable!("invalid hex escape"), + }; + (ch, &s[2..]) +} + +fn backslash_u(mut s: &str) -> (char, &str) { + s = &s[1..]; + + let mut ch = 0; + let mut digits = 0; + loop { + let b = byte(s, 0); + let digit = match b { + b'0'..=b'9' => b - b'0', + b'a'..=b'f' => 10 + b - b'a', + b'A'..=b'F' => 10 + b - b'A', + b'_' if digits > 0 => { + s = &s[1..]; + continue; + } + b'}' if digits != 0 => break, + _ => unreachable!("invalid unicode escape"), + }; + ch *= 0x10; + ch += u32::from(digit); + digits += 1; + s = &s[1..]; + } + s = &s[1..]; + + ( + char::from_u32(ch).expect("invalid unicode escape passed by compiler"), + s, + ) +} diff --git a/third_party/rust/time-macros/src/lib.rs b/third_party/rust/time-macros/src/lib.rs new file mode 100644 index 0000000000..1afc313eaf --- /dev/null +++ b/third_party/rust/time-macros/src/lib.rs @@ -0,0 +1,167 @@ +#![deny( + anonymous_parameters, + clippy::all, + const_err, + illegal_floating_point_literal_pattern, + late_bound_lifetime_arguments, + path_statements, + patterns_in_fns_without_body, + rust_2018_idioms, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_code, + unused_extern_crates +)] +#![warn( + clippy::dbg_macro, + clippy::decimal_literal_representation, + clippy::get_unwrap, + clippy::nursery, + clippy::print_stdout, + clippy::todo, + clippy::unimplemented, + clippy::unnested_or_patterns, + clippy::unwrap_used, + clippy::use_debug, + single_use_lifetimes, + unused_qualifications, + variant_size_differences +)] +#![allow( + clippy::missing_const_for_fn, // useless in proc macro + clippy::redundant_pub_crate, // suggests bad style + clippy::option_if_let_else, // suggests terrible code +)] + +#[macro_use] +mod quote; + +mod date; +mod datetime; +mod error; +#[cfg(any(feature = "formatting", feature = "parsing"))] +mod format_description; +mod helpers; +mod offset; +#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))] +mod serde_format_description; +mod time; +mod to_tokens; + +use proc_macro::TokenStream; +#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))] +use proc_macro::TokenTree; + +use self::error::Error; + +macro_rules! impl_macros { + ($($name:ident)*) => {$( + #[proc_macro] + pub fn $name(input: TokenStream) -> TokenStream { + use crate::to_tokens::ToTokenTree; + + let mut iter = input.into_iter().peekable(); + match $name::parse(&mut iter) { + Ok(value) => match iter.peek() { + Some(tree) => Error::UnexpectedToken { tree: tree.clone() }.to_compile_error(), + None => TokenStream::from(value.into_token_tree()), + }, + Err(err) => err.to_compile_error(), + } + } + )*}; +} + +impl_macros![date datetime offset time]; + +#[cfg(any(feature = "formatting", feature = "parsing"))] +#[proc_macro] +pub fn format_description(input: TokenStream) -> TokenStream { + (|| { + let (span, string) = helpers::get_string_literal(input)?; + let items = format_description::parse(&string, span)?; + + Ok(quote! {{ + const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = &[#S( + items + .into_iter() + .map(|item| quote! { #S(item), }) + .collect::<TokenStream>() + )]; + DESCRIPTION + }}) + })() + .unwrap_or_else(|err: Error| err.to_compile_error()) +} + +#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))] +#[proc_macro] +pub fn serde_format_description(input: TokenStream) -> TokenStream { + (|| { + let mut tokens = input.into_iter().peekable(); + // First, an identifier (the desired module name) + let mod_name = match tokens.next() { + Some(TokenTree::Ident(ident)) => Ok(ident), + Some(tree) => Err(Error::UnexpectedToken { tree }), + None => Err(Error::UnexpectedEndOfInput), + }?; + + // Followed by a comma + helpers::consume_punct(',', &mut tokens)?; + + // Then, the type to create serde serializers for (e.g., `OffsetDateTime`). + let formattable = match tokens.next() { + Some(tree @ TokenTree::Ident(_)) => Ok(tree), + Some(tree) => Err(Error::UnexpectedToken { tree }), + None => Err(Error::UnexpectedEndOfInput), + }?; + + // Another comma + helpers::consume_punct(',', &mut tokens)?; + + // We now have two options. The user can either provide a format description as a string or + // they can provide a path to a format description. If the latter, all remaining tokens are + // assumed to be part of the path. + let (format, raw_format_string) = match tokens.peek() { + // string literal + Some(TokenTree::Literal(_)) => { + let (span, format_string) = helpers::get_string_literal(tokens.collect())?; + let items = format_description::parse(&format_string, span)?; + let items: TokenStream = + items.into_iter().map(|item| quote! { #S(item), }).collect(); + let items = quote! { &[#S(items)] }; + + ( + items, + Some(String::from_utf8_lossy(&format_string).into_owned()), + ) + } + // path + Some(_) => ( + quote! {{ + // We can't just do `super::path` because the path could be an absolute path. In + // that case, we'd be generating `super::::path`, which is invalid. Even if we + // took that into account, it's not possible to know if it's an external crate, + // which would just require emitting `path` directly. By taking this approach, + // we can leave it to the compiler to do the actual resolution. + mod __path_hack { + pub(super) use super::super::*; + pub(super) use #S(tokens.collect::<TokenStream>()) as FORMAT; + } + __path_hack::FORMAT + }}, + None, + ), + None => return Err(Error::UnexpectedEndOfInput), + }; + + Ok(serde_format_description::build( + mod_name, + formattable, + format, + raw_format_string, + )) + })() + .unwrap_or_else(|err: Error| err.to_compile_error_standalone()) +} diff --git a/third_party/rust/time-macros/src/offset.rs b/third_party/rust/time-macros/src/offset.rs new file mode 100644 index 0000000000..c2099073f8 --- /dev/null +++ b/third_party/rust/time-macros/src/offset.rs @@ -0,0 +1,95 @@ +use std::iter::Peekable; + +use proc_macro::{token_stream, Span, TokenTree}; + +use crate::helpers::{consume_any_ident, consume_number, consume_punct}; +use crate::to_tokens::ToTokenTree; +use crate::Error; + +pub(crate) struct Offset { + pub(crate) hours: i8, + pub(crate) minutes: i8, + pub(crate) seconds: i8, +} + +pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Offset, Error> { + if consume_any_ident(&["utc", "UTC"], chars).is_ok() { + return Ok(Offset { + hours: 0, + minutes: 0, + seconds: 0, + }); + } + + let sign = if consume_punct('+', chars).is_ok() { + 1 + } else if consume_punct('-', chars).is_ok() { + -1 + } else if let Some(tree) = chars.next() { + return Err(Error::UnexpectedToken { tree }); + } else { + return Err(Error::MissingComponent { + name: "sign", + span_start: None, + span_end: None, + }); + }; + + let (hours_span, hours) = consume_number::<i8>("hour", chars)?; + let (mut minutes_span, mut minutes) = (Span::mixed_site(), 0); + let (mut seconds_span, mut seconds) = (Span::mixed_site(), 0); + + if consume_punct(':', chars).is_ok() { + let min = consume_number::<i8>("minute", chars)?; + minutes_span = min.0; + minutes = min.1; + + if consume_punct(':', chars).is_ok() { + let sec = consume_number::<i8>("second", chars)?; + seconds_span = sec.0; + seconds = sec.1; + } + } + + if hours >= 24 { + Err(Error::InvalidComponent { + name: "hour", + value: hours.to_string(), + span_start: Some(hours_span), + span_end: Some(hours_span), + }) + } else if minutes >= 60 { + Err(Error::InvalidComponent { + name: "minute", + value: minutes.to_string(), + span_start: Some(minutes_span), + span_end: Some(minutes_span), + }) + } else if seconds >= 60 { + Err(Error::InvalidComponent { + name: "second", + value: seconds.to_string(), + span_start: Some(seconds_span), + span_end: Some(seconds_span), + }) + } else { + Ok(Offset { + hours: sign * hours, + minutes: sign * minutes, + seconds: sign * seconds, + }) + } +} + +impl ToTokenTree for Offset { + fn into_token_tree(self) -> TokenTree { + quote_group! {{ + const OFFSET: ::time::UtcOffset = ::time::UtcOffset::__from_hms_unchecked( + #(self.hours), + #(self.minutes), + #(self.seconds), + ); + OFFSET + }} + } +} diff --git a/third_party/rust/time-macros/src/quote.rs b/third_party/rust/time-macros/src/quote.rs new file mode 100644 index 0000000000..2fe86cc98d --- /dev/null +++ b/third_party/rust/time-macros/src/quote.rs @@ -0,0 +1,134 @@ +macro_rules! quote { + () => (::proc_macro::TokenStream::new()); + ($($x:tt)*) => {{ + let mut ts = ::proc_macro::TokenStream::new(); + let ts_mut = &mut ts; + quote_inner!(ts_mut $($x)*); + ts + }}; +} + +#[cfg(any(feature = "formatting", feature = "parsing"))] +macro_rules! quote_append { + ($ts:ident $($x:tt)*) => {{ + quote_inner!($ts $($x)*); + }}; +} + +macro_rules! quote_group { + ({ $($x:tt)* }) => { + ::proc_macro::TokenTree::Group(::proc_macro::Group::new( + ::proc_macro::Delimiter::Brace, + quote!($($x)*) + )) + }; +} + +macro_rules! sym { + ($ts:ident $x:tt $y:tt) => { + $ts.extend([ + ::proc_macro::TokenTree::from(::proc_macro::Punct::new( + $x, + ::proc_macro::Spacing::Joint, + )), + ::proc_macro::TokenTree::from(::proc_macro::Punct::new( + $y, + ::proc_macro::Spacing::Alone, + )), + ]); + }; + ($ts:ident $x:tt) => { + $ts.extend([::proc_macro::TokenTree::from(::proc_macro::Punct::new( + $x, + ::proc_macro::Spacing::Alone, + ))]); + }; +} + +macro_rules! quote_inner { + // Base case + ($ts:ident) => {}; + + // Single or double symbols + ($ts:ident :: $($tail:tt)*) => { sym!($ts ':' ':'); quote_inner!($ts $($tail)*); }; + ($ts:ident .. $($tail:tt)*) => { sym!($ts '.' '.'); quote_inner!($ts $($tail)*); }; + ($ts:ident : $($tail:tt)*) => { sym!($ts ':'); quote_inner!($ts $($tail)*); }; + ($ts:ident = $($tail:tt)*) => { sym!($ts '='); quote_inner!($ts $($tail)*); }; + ($ts:ident ; $($tail:tt)*) => { sym!($ts ';'); quote_inner!($ts $($tail)*); }; + ($ts:ident , $($tail:tt)*) => { sym!($ts ','); quote_inner!($ts $($tail)*); }; + ($ts:ident . $($tail:tt)*) => { sym!($ts '.'); quote_inner!($ts $($tail)*); }; + ($ts:ident & $($tail:tt)*) => { sym!($ts '&'); quote_inner!($ts $($tail)*); }; + ($ts:ident << $($tail:tt)*) => { sym!($ts '<' '<'); quote_inner!($ts $($tail)*); }; + ($ts:ident < $($tail:tt)*) => { sym!($ts '<'); quote_inner!($ts $($tail)*); }; + ($ts:ident >> $($tail:tt)*) => { sym!($ts '>' '>'); quote_inner!($ts $($tail)*); }; + ($ts:ident > $($tail:tt)*) => { sym!($ts '>'); quote_inner!($ts $($tail)*); }; + ($ts:ident -> $($tail:tt)*) => { sym!($ts '-' '>'); quote_inner!($ts $($tail)*); }; + ($ts:ident ? $($tail:tt)*) => { sym!($ts '?'); quote_inner!($ts $($tail)*); }; + ($ts:ident ! $($tail:tt)*) => { sym!($ts '!'); quote_inner!($ts $($tail)*); }; + ($ts:ident | $($tail:tt)*) => { sym!($ts '|'); quote_inner!($ts $($tail)*); }; + ($ts:ident * $($tail:tt)*) => { sym!($ts '*'); quote_inner!($ts $($tail)*); }; + + // Identifier + ($ts:ident $i:ident $($tail:tt)*) => { + $ts.extend([::proc_macro::TokenTree::from(::proc_macro::Ident::new( + &stringify!($i), + ::proc_macro::Span::mixed_site(), + ))]); + quote_inner!($ts $($tail)*); + }; + + // Literal + ($ts:ident $l:literal $($tail:tt)*) => { + $ts.extend([::proc_macro::TokenTree::from(::proc_macro::Literal::string(&$l))]); + quote_inner!($ts $($tail)*); + }; + + // Lifetime + ($ts:ident $l:lifetime $($tail:tt)*) => { + $ts.extend([ + ::proc_macro::TokenTree::from( + ::proc_macro::Punct::new('\'', ::proc_macro::Spacing::Joint) + ), + ::proc_macro::TokenTree::from(::proc_macro::Ident::new( + stringify!($l).trim_start_matches(|c| c == '\''), + ::proc_macro::Span::mixed_site(), + )), + ]); + quote_inner!($ts $($tail)*); + }; + + // Groups + ($ts:ident ($($inner:tt)*) $($tail:tt)*) => { + $ts.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new( + ::proc_macro::Delimiter::Parenthesis, + quote!($($inner)*) + ))]); + quote_inner!($ts $($tail)*); + }; + ($ts:ident [$($inner:tt)*] $($tail:tt)*) => { + $ts.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new( + ::proc_macro::Delimiter::Bracket, + quote!($($inner)*) + ))]); + quote_inner!($ts $($tail)*); + }; + ($ts:ident {$($inner:tt)*} $($tail:tt)*) => { + $ts.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new( + ::proc_macro::Delimiter::Brace, + quote!($($inner)*) + ))]); + quote_inner!($ts $($tail)*); + }; + + // Interpolated values + // TokenTree by default + ($ts:ident #($e:expr) $($tail:tt)*) => { + $ts.extend([$crate::to_tokens::ToTokenTree::into_token_tree($e)]); + quote_inner!($ts $($tail)*); + }; + // Allow a TokenStream by request. It's more expensive, so avoid if possible. + ($ts:ident #S($e:expr) $($tail:tt)*) => { + $crate::to_tokens::ToTokenStream::append_to($e, $ts); + quote_inner!($ts $($tail)*); + }; +} diff --git a/third_party/rust/time-macros/src/serde_format_description.rs b/third_party/rust/time-macros/src/serde_format_description.rs new file mode 100644 index 0000000000..c09a4e9e2a --- /dev/null +++ b/third_party/rust/time-macros/src/serde_format_description.rs @@ -0,0 +1,163 @@ +use proc_macro::{Ident, TokenStream, TokenTree}; + +pub(crate) fn build( + mod_name: Ident, + ty: TokenTree, + format: TokenStream, + raw_format_string: Option<String>, +) -> TokenStream { + let ty_s = &*ty.to_string(); + + let format_description_display = raw_format_string.unwrap_or_else(|| format.to_string()); + + let visitor = if cfg!(feature = "parsing") { + quote! { + struct Visitor; + struct OptionVisitor; + + impl<'a> ::serde::de::Visitor<'a> for Visitor { + type Value = __TimeSerdeType; + + fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + write!( + f, + concat!( + "a(n) `", + #(ty_s), + "` in the format \"{}\"", + ), + #(format_description_display.as_str()) + ) + } + + fn visit_str<E: ::serde::de::Error>( + self, + value: &str + ) -> Result<__TimeSerdeType, E> { + __TimeSerdeType::parse(value, &DESCRIPTION).map_err(E::custom) + } + } + + impl<'a> ::serde::de::Visitor<'a> for OptionVisitor { + type Value = Option<__TimeSerdeType>; + + fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + write!( + f, + concat!( + "an `Option<", + #(ty_s), + ">` in the format \"{}\"", + ), + #(format_description_display.as_str()) + ) + } + + fn visit_some<D: ::serde::de::Deserializer<'a>>( + self, + deserializer: D + ) -> Result<Option<__TimeSerdeType>, D::Error> { + deserializer + .deserialize_any(Visitor) + .map(Some) + } + + fn visit_none<E: ::serde::de::Error>( + self + ) -> Result<Option<__TimeSerdeType>, E> { + Ok(None) + } + } + } + } else { + quote!() + }; + + let serialize_primary = if cfg!(feature = "formatting") { + quote! { + pub fn serialize<S: ::serde::Serializer>( + datetime: &__TimeSerdeType, + serializer: S, + ) -> Result<S::Ok, S::Error> { + use ::serde::Serialize; + datetime + .format(&DESCRIPTION) + .map_err(::time::error::Format::into_invalid_serde_value::<S>)? + .serialize(serializer) + } + } + } else { + quote!() + }; + + let deserialize_primary = if cfg!(feature = "parsing") { + quote! { + pub fn deserialize<'a, D: ::serde::Deserializer<'a>>( + deserializer: D + ) -> Result<__TimeSerdeType, D::Error> { + use ::serde::Deserialize; + deserializer.deserialize_any(Visitor) + } + } + } else { + quote!() + }; + + let serialize_option = if cfg!(feature = "formatting") { + quote! { + pub fn serialize<S: ::serde::Serializer>( + option: &Option<__TimeSerdeType>, + serializer: S, + ) -> Result<S::Ok, S::Error> { + use ::serde::Serialize; + option.map(|datetime| datetime.format(&DESCRIPTION)) + .transpose() + .map_err(::time::error::Format::into_invalid_serde_value::<S>)? + .serialize(serializer) + } + } + } else { + quote!() + }; + + let deserialize_option = if cfg!(feature = "parsing") { + quote! { + pub fn deserialize<'a, D: ::serde::Deserializer<'a>>( + deserializer: D + ) -> Result<Option<__TimeSerdeType>, D::Error> { + use ::serde::Deserialize; + deserializer.deserialize_option(OptionVisitor) + } + } + } else { + quote!() + }; + + let deserialize_option_imports = if cfg!(feature = "parsing") { + quote! { + use super::{OptionVisitor, Visitor}; + } + } else { + quote!() + }; + + quote! { + mod #(mod_name) { + use ::time::#(ty) as __TimeSerdeType; + + const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = #S(format); + + #S(visitor) + #S(serialize_primary) + #S(deserialize_primary) + + pub(super) mod option { + use super::{DESCRIPTION, __TimeSerdeType}; + #S(deserialize_option_imports) + + #S(serialize_option) + #S(deserialize_option) + } + } + } +} diff --git a/third_party/rust/time-macros/src/time.rs b/third_party/rust/time-macros/src/time.rs new file mode 100644 index 0000000000..719e2051f1 --- /dev/null +++ b/third_party/rust/time-macros/src/time.rs @@ -0,0 +1,118 @@ +use std::iter::Peekable; + +use proc_macro::{token_stream, Span, TokenTree}; + +use crate::helpers::{consume_any_ident, consume_number, consume_punct}; +use crate::to_tokens::ToTokenTree; +use crate::Error; + +enum Period { + Am, + Pm, + _24, +} + +pub(crate) struct Time { + pub(crate) hour: u8, + pub(crate) minute: u8, + pub(crate) second: u8, + pub(crate) nanosecond: u32, +} + +pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Time, Error> { + fn consume_period(chars: &mut Peekable<token_stream::IntoIter>) -> (Option<Span>, Period) { + if let Ok(span) = consume_any_ident(&["am", "AM"], chars) { + (Some(span), Period::Am) + } else if let Ok(span) = consume_any_ident(&["pm", "PM"], chars) { + (Some(span), Period::Pm) + } else { + (None, Period::_24) + } + } + + let (hour_span, hour) = consume_number("hour", chars)?; + + let ((minute_span, minute), (second_span, second), (period_span, period)) = + match consume_period(chars) { + // Nothing but the 12-hour clock hour and AM/PM + (period_span @ Some(_), period) => ( + (Span::mixed_site(), 0), + (Span::mixed_site(), 0.), + (period_span, period), + ), + (None, _) => { + consume_punct(':', chars)?; + let (minute_span, minute) = consume_number::<u8>("minute", chars)?; + let (second_span, second): (_, f64) = if consume_punct(':', chars).is_ok() { + consume_number("second", chars)? + } else { + (Span::mixed_site(), 0.) + }; + let (period_span, period) = consume_period(chars); + ( + (minute_span, minute), + (second_span, second), + (period_span, period), + ) + } + }; + + let hour = match (hour, period) { + (0, Period::Am | Period::Pm) => { + return Err(Error::InvalidComponent { + name: "hour", + value: hour.to_string(), + span_start: Some(hour_span), + span_end: Some(period_span.unwrap_or(hour_span)), + }); + } + (12, Period::Am) => 0, + (12, Period::Pm) => 12, + (hour, Period::Am | Period::_24) => hour, + (hour, Period::Pm) => hour + 12, + }; + + if hour >= 24 { + Err(Error::InvalidComponent { + name: "hour", + value: hour.to_string(), + span_start: Some(hour_span), + span_end: Some(period_span.unwrap_or(hour_span)), + }) + } else if minute >= 60 { + Err(Error::InvalidComponent { + name: "minute", + value: minute.to_string(), + span_start: Some(minute_span), + span_end: Some(minute_span), + }) + } else if second >= 60. { + Err(Error::InvalidComponent { + name: "second", + value: second.to_string(), + span_start: Some(second_span), + span_end: Some(second_span), + }) + } else { + Ok(Time { + hour, + minute, + second: second.trunc() as _, + nanosecond: (second.fract() * 1_000_000_000.).round() as _, + }) + } +} + +impl ToTokenTree for Time { + fn into_token_tree(self) -> TokenTree { + quote_group! {{ + const TIME: ::time::Time = ::time::Time::__from_hms_nanos_unchecked( + #(self.hour), + #(self.minute), + #(self.second), + #(self.nanosecond), + ); + TIME + }} + } +} diff --git a/third_party/rust/time-macros/src/to_tokens.rs b/third_party/rust/time-macros/src/to_tokens.rs new file mode 100644 index 0000000000..3a293925c9 --- /dev/null +++ b/third_party/rust/time-macros/src/to_tokens.rs @@ -0,0 +1,68 @@ +use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree}; + +pub(crate) trait ToTokenStream: Sized { + fn append_to(self, ts: &mut TokenStream); +} + +pub(crate) trait ToTokenTree: Sized { + fn into_token_tree(self) -> TokenTree; +} + +impl<T: ToTokenTree> ToTokenStream for T { + fn append_to(self, ts: &mut TokenStream) { + ts.extend([self.into_token_tree()]) + } +} + +impl ToTokenTree for bool { + fn into_token_tree(self) -> TokenTree { + let lit = if self { "true" } else { "false" }; + TokenTree::Ident(Ident::new(lit, Span::mixed_site())) + } +} + +impl ToTokenStream for TokenStream { + fn append_to(self, ts: &mut TokenStream) { + ts.extend(self) + } +} + +impl ToTokenTree for TokenTree { + fn into_token_tree(self) -> TokenTree { + self + } +} + +impl ToTokenTree for &str { + fn into_token_tree(self) -> TokenTree { + TokenTree::Literal(Literal::string(self)) + } +} + +macro_rules! impl_for_tree_types { + ($($type:ty)*) => {$( + impl ToTokenTree for $type { + fn into_token_tree(self) -> TokenTree { + TokenTree::from(self) + } + } + )*}; +} +impl_for_tree_types![Ident Literal Group Punct]; + +macro_rules! impl_for_int { + ($($type:ty => $method:ident)*) => {$( + impl ToTokenTree for $type { + fn into_token_tree(self) -> TokenTree { + TokenTree::from(Literal::$method(self)) + } + } + )*}; +} +impl_for_int! { + i8 => i8_unsuffixed + u8 => u8_unsuffixed + u16 => u16_unsuffixed + i32 => i32_unsuffixed + u32 => u32_unsuffixed +} diff --git a/third_party/rust/time/.cargo-checksum.json b/third_party/rust/time/.cargo-checksum.json new file mode 100644 index 0000000000..cb92bb424b --- /dev/null +++ b/third_party/rust/time/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"9310f48e6d4dc1bae156b3bc0b8cbbcfe0069f5f3724e8ce1840589214631763","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","README.md":"255d27d2cbd3668cadc6ad6e2b5ad3ae737e3fdba90e5a2ec7c538e94a9a4ff6","src/date.rs":"609cab5429ee0141a6d475f4548b6ec166ef512d79b283c149f415a82f9f1579","src/duration.rs":"bc566d667adb286f190fd125c4c4d034bb02702f02b8077be05b81deb95f22c7","src/error/component_range.rs":"26a1aa4ea2d0f9887efcbe9584d5aa14b1e5d37525a52dc9f18e1e282599625d","src/error/conversion_range.rs":"972abb765370070de01e2fc2e1bb1e80808a069e6213577d7beaca02e1d707c3","src/error/different_variant.rs":"107bef7b3addd7108b36a2da8389f611d4482f34a5b63429841141e05c8cb30c","src/error/format.rs":"d87846c2ac62dec421402ea21e5d2a8d73add6658df4ac914067a4b43cb0ef20","src/error/indeterminate_offset.rs":"1f52f9ea107847fa781399cfcc8046451d70155fb497486c80b2138f82782941","src/error/invalid_format_description.rs":"c36f873d8823ee6aa3eded98fdf9f3c71572e24eb3c8e8a584bbb2195ee27c2d","src/error/invalid_variant.rs":"b653a3e6e902f06cb9f2e0366c4da84b92e8bdb03164c2f8cb15fe66415706e4","src/error/mod.rs":"15fb848b1919d9cfb50fb9091abfcea6a8c7db5a2fcd6cb8f32c4af5f1ea4464","src/error/parse.rs":"3bdc8201a14469d2cc7a12a295058569098f9cfc9bd1e8fc9f526ada8298e4f8","src/error/parse_from_description.rs":"990359eb5fcb64c1ee363b044147b7330a92a4cb7373dc2f17f6fd3bcc6411a0","src/error/try_from_parsed.rs":"8c227be52653a1d33af01a8024c0fc56f1f9803f08ef01487a7eaa5833adbb57","src/ext.rs":"03e08906d328f18701237eccd3e220dfa82bc21290ff386b8440588d30dd192b","src/format_description/borrowed_format_item.rs":"afab66e65a84895751d3557fc5b8a3a5e63f9c483a6a534aa4f86fd2a5145f0b","src/format_description/component.rs":"8e39e93bf27c53f4b1debdc427741621ea6503c0343a6c762316069e1b7a82cc","src/format_description/mod.rs":"d3c53d6e8d183cd9168204b58df075ad1118780aa127b04480e1c351979edbf5","src/format_description/modifier.rs":"b013c71c59977b51d989b48adc7758a40fc9d05511c13070eb3849cefd2ab46a","src/format_description/owned_format_item.rs":"419f5354bf504562c9225dfe90b61eee9bc959211a86a327197b4f54283da775","src/format_description/parse/ast.rs":"a3614bc4206d8d09824f4f98dd29bed2c90fa6336510bde3b087f8880e925dc1","src/format_description/parse/format_item.rs":"d00246f892a7e88fe017b29bb757cd2b98631b97cc87749db41ce48781fd50ee","src/format_description/parse/lexer.rs":"0390142bf16a242d6eb82c19ec9807a8a3c1e05a629bdddda1a68c0c6eabad9f","src/format_description/parse/mod.rs":"3942c8248cc84fb45cae7b0b2552e47b95638235a799002b8932a26906769b5b","src/format_description/well_known/iso8601.rs":"8313905039a637d4d132f8318a59c06246e7b61550b4e4bc7d129232ac022e43","src/format_description/well_known/iso8601/adt_hack.rs":"82c308ea2f7ae87f7cc9e01e931cdf1633d89bf0002403b66cb041059d6f5873","src/format_description/well_known/rfc2822.rs":"36c23394724ae12250d4193cab26887a6ff8f82ca441ea6b0d03c4f1c928b3dd","src/format_description/well_known/rfc3339.rs":"1a6318dffd3ebb6ac7cf96eae3d9b1eb44b1089cf4284fa6a7e935c6fcf1b43c","src/formatting/formattable.rs":"a95d0b4f6e083bc061ae915391e7990b07e861aca9913e75595856d95178ce6f","src/formatting/iso8601.rs":"dda61b56e8d33071d9cb6e70dca61de89c2e4abc555bdbdfc1ad72ad3c0611ac","src/formatting/mod.rs":"cbfed6ee8a21d3832b34b1ba168d750ffa0927a71bf813fc8c4032a72f6a17fd","src/instant.rs":"15f8d497d71730a9bbd2103992eec8f626e2f632486564fdd641a72c4e20a21d","src/lib.rs":"d5bb5e86a350b64e0a92f5630b28c54692030145abe389a3abfe3e7499ccbdce","src/macros.rs":"eb9e02a1f97bb8befab7bc27c937136817e4f65e0b3e040a81394ae938980558","src/month.rs":"a9fdc0bc4c8f668a69edb8e51ea2c0f48ac801ace0a7332abb6983282b2fba43","src/offset_date_time.rs":"83935b393f31e15c82139ceca465834c4319ef7895f93c294679d4977530fa82","src/parsing/combinator/mod.rs":"b342fbd95dd986309d81e8910363920ba6db00958b459f6d97f57da3ae3e550d","src/parsing/combinator/rfc/iso8601.rs":"13289a0d58de273327830a3001167a8964edc5045486301efdf3ddc2e4079c32","src/parsing/combinator/rfc/mod.rs":"f30b75d248f5ae92c27646d504703f5489185afb76c998cc4375437b3d15c822","src/parsing/combinator/rfc/rfc2234.rs":"08e2813c6d40c0dae881875fe0417ae06886c73679256587e33186e46b3c3bae","src/parsing/combinator/rfc/rfc2822.rs":"2aff3a6a2778bc806031cff92ad2f43f0874620b5d484b5b39ee2d2507212f06","src/parsing/component.rs":"067d9ee045eb4424f213cf0e48c571d2c8d80839373bbf4bab575942d8dbc28c","src/parsing/iso8601.rs":"30c49e23939d827decfd01c223425c73085d32eaddd397d181652a1a210f442e","src/parsing/mod.rs":"37082ac824c6c3f4900766a0a3140dc7aa46b3f85cb6098f11da7da333e421b0","src/parsing/parsable.rs":"47c99751470e1334233b3fe67161ff61e9c6e63dad4c66273e1a378164bf2084","src/parsing/parsed.rs":"3b2adbf6b076b730e38ee23aa034b0061ae40a3d660f877a7227b56ed50cdc2e","src/parsing/shim.rs":"800be565765a317f4515e598b37d69850e1f90a89ff16e3650a9fd92a58239fe","src/primitive_date_time.rs":"187932764c7a9b0f551d3c4dce832f1f8d8560c3aaa35e0a09356b744174c68c","src/quickcheck.rs":"eb0b4ae369d9fdba0ddb9d3a30b6f4179d9f123513d2cbd5a55962c89bee8ef2","src/rand.rs":"ebee80c9d4301229eef91cb0574eeecaf1157d7cf806dece0bb202ee8af2bda7","src/serde/iso8601.rs":"d77e588d4ede08e4e5ddb3a281184802fdc80ce3f263a1b946871fbc13162633","src/serde/mod.rs":"b1c0b69974d33ccd55c4080647f3fa0819f2a9df7299e3dc8700fe6cd338c704","src/serde/rfc2822.rs":"fe97aa1311037a362eb477fe8c6729b3b85ff2d0afab7148f10f64d109081f90","src/serde/rfc3339.rs":"9835c8b8fb24b53657769b81a71188fe4261e5869917779e1702b3a0aa854654","src/serde/timestamp.rs":"30971ad5d1fef11e396eee48d476b828ed4e99f6eac587383b864dd95c120fe4","src/serde/visitor.rs":"6a2a10cfe5afa59f7c8f02585c589514a9fbafdac538b2557a0571f00a0858b7","src/sys/local_offset_at/imp.rs":"4b6e57f02566364270ac9b7e1540290a5658a296f7e911f988264d103e420326","src/sys/local_offset_at/mod.rs":"501a5d07f6f8852d72aeab6d1aae3630de149cbb182bf10184fb6764a46e5baa","src/sys/local_offset_at/unix.rs":"18aa655a365148f0bd833840c36ee4f50df0b31d83f66d6e6c2b7a4b0aabecef","src/sys/local_offset_at/wasm_js.rs":"7f6112bf7fd702b546189aca8fbcdb51f63b7e5d3af9d37cb95242aa07d989b1","src/sys/local_offset_at/windows.rs":"35919ea5b0533ca94910875043a01b6dac8f8cfc77a8c3e8f36cc7ced0a77aa1","src/sys/mod.rs":"0a43797e55e986233a71f1cc4b3a21997da42bc15db7d912373296cd535e49bc","src/tests.rs":"b2f6ca74f4b688a464fb55bb0e6a2e9033f393daff3b44121a6fd01063594b07","src/time.rs":"f985e0cc4dfaa87d45a7e73eb4b1331f193c7957823eb35f30f4d024cc1384a3","src/utc_offset.rs":"30517bc224b5d01a17f11856f8b27f38d7a371a60b06fa33071af7bb00db3a7f","src/util.rs":"d84521c576b236c4b86b7798532b02e38261dfed8790ef435e11a68fe9beed7c","src/weekday.rs":"2ee21c78f6de7cd5db50affb06da4f78fbe84e03007402c8919920734eca10c7"},"package":"a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"}
\ No newline at end of file diff --git a/third_party/rust/time/Cargo.toml b/third_party/rust/time/Cargo.toml new file mode 100644 index 0000000000..8ff2173f69 --- /dev/null +++ b/third_party/rust/time/Cargo.toml @@ -0,0 +1,166 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.60.0" +name = "time" +version = "0.3.17" +authors = [ + "Jacob Pratt <open-source@jhpratt.dev>", + "Time contributors", +] +include = [ + "src/**/*", + "LICENSE-*", + "README.md", +] +description = "Date and time library. Fully interoperable with the standard library. Mostly compatible with #![no_std]." +homepage = "https://time-rs.github.io" +readme = "README.md" +keywords = [ + "date", + "time", + "calendar", + "duration", +] +categories = [ + "date-and-time", + "no-std", + "parser-implementations", + "value-formatting", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/time-rs/time" + +[package.metadata.docs.rs] +all-features = true +targets = ["x86_64-unknown-linux-gnu"] +rustdoc-args = [ + "--cfg", + "__time_03_docs", +] + +[lib] +bench = false + +[[test]] +name = "tests" +path = "../tests/main.rs" + +[[bench]] +name = "benchmarks" +path = "../benchmarks/main.rs" +harness = false + +[dependencies.itoa] +version = "1.0.1" +optional = true + +[dependencies.quickcheck] +version = "1.0.3" +optional = true +default-features = false + +[dependencies.rand] +version = "0.8.4" +optional = true +default-features = false + +[dependencies.serde] +version = "1.0.126" +optional = true +default-features = false + +[dependencies.time-core] +version = "=0.1.0" + +[dependencies.time-macros] +version = "=0.2.6" +optional = true + +[dev-dependencies.quickcheck_macros] +version = "1.0.0" + +[dev-dependencies.rand] +version = "0.8.4" +default-features = false + +[dev-dependencies.serde] +version = "1.0.126" +features = ["derive"] +default-features = false + +[dev-dependencies.serde_json] +version = "1.0.68" + +[dev-dependencies.serde_test] +version = "1.0.126" + +[dev-dependencies.time-macros] +version = "=0.2.6" + +[features] +alloc = ["serde?/alloc"] +default = ["std"] +formatting = [ + "dep:itoa", + "std", + "time-macros?/formatting", +] +large-dates = ["time-macros?/large-dates"] +local-offset = [ + "std", + "dep:libc", + "dep:num_threads", +] +macros = ["dep:time-macros"] +parsing = ["time-macros?/parsing"] +quickcheck = [ + "dep:quickcheck", + "alloc", +] +rand = ["dep:rand"] +serde = [ + "dep:serde", + "time-macros?/serde", +] +serde-human-readable = [ + "serde", + "formatting", + "parsing", +] +serde-well-known = [ + "serde", + "formatting", + "parsing", +] +std = ["alloc"] +wasm-bindgen = ["dep:js-sys"] + +[target."cfg(__ui_tests)".dev-dependencies.trybuild] +version = "1.0.68" + +[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dependencies.js-sys] +version = "0.3.58" +optional = true + +[target."cfg(bench)".dev-dependencies.criterion] +version = "0.4.0" +default-features = false + +[target."cfg(target_family = \"unix\")".dependencies.libc] +version = "0.2.98" +optional = true + +[target."cfg(target_family = \"unix\")".dependencies.num_threads] +version = "0.1.2" +optional = true diff --git a/third_party/rust/time/LICENSE-Apache b/third_party/rust/time/LICENSE-Apache new file mode 100644 index 0000000000..7646f21e37 --- /dev/null +++ b/third_party/rust/time/LICENSE-Apache @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Jacob Pratt et al. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/rust/time/LICENSE-MIT b/third_party/rust/time/LICENSE-MIT new file mode 100644 index 0000000000..a11a755732 --- /dev/null +++ b/third_party/rust/time/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2022 Jacob Pratt et al. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/rust/time/README.md b/third_party/rust/time/README.md new file mode 100644 index 0000000000..34b5753408 --- /dev/null +++ b/third_party/rust/time/README.md @@ -0,0 +1,42 @@ +# time + +[![minimum rustc: 1.60](https://img.shields.io/badge/minimum%20rustc-1.60-yellowgreen?logo=rust&style=flat-square)](https://www.whatrustisit.com) +[![version](https://img.shields.io/crates/v/time?color=blue&logo=rust&style=flat-square)](https://crates.io/crates/time) +[![build status](https://img.shields.io/github/workflow/status/time-rs/time/Build/main?style=flat-square)](https://github.com/time-rs/time/actions) +[![codecov](https://codecov.io/gh/time-rs/time/branch/main/graph/badge.svg?token=yt4XSmQNKQ)](https://codecov.io/gh/time-rs/time) + +Documentation: + +- [latest release](https://docs.rs/time) +- [main branch](https://time-rs.github.io/api/time) +- [book](https://time-rs.github.io/book) + +## Minimum Rust version policy + +The time crate is guaranteed to compile with any release of rustc from the past six months. +Optional feature flags that enable interoperability with third-party crates (e.g. rand) +follow the policy of that crate if stricter. + +## Contributing + +Contributions are always welcome! If you have an idea, it's best to float it by me before working on +it to ensure no effort is wasted. If there's already an open issue for it, knock yourself out. +Internal documentation can be viewed [here](https://time-rs.github.io/internal-api/time). + +If you have any questions, feel free to use [Discussions]. Don't hesitate to ask questions — that's +what I'm here for! + +[Discussions]: https://github.com/time-rs/time/discussions + +## License + +This project is licensed under either of + +- [Apache License, Version 2.0](https://github.com/time-rs/time/blob/main/LICENSE-Apache) +- [MIT license](https://github.com/time-rs/time/blob/main/LICENSE-MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +time by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/third_party/rust/time/src/date.rs b/third_party/rust/time/src/date.rs new file mode 100644 index 0000000000..b3023e0c05 --- /dev/null +++ b/third_party/rust/time/src/date.rs @@ -0,0 +1,1050 @@ +//! The [`Date`] struct and its associated `impl`s. + +use core::fmt; +use core::ops::{Add, Sub}; +use core::time::Duration as StdDuration; +#[cfg(feature = "formatting")] +use std::io; + +#[cfg(feature = "formatting")] +use crate::formatting::Formattable; +#[cfg(feature = "parsing")] +use crate::parsing::Parsable; +use crate::util::{days_in_year, days_in_year_month, is_leap_year, weeks_in_year}; +use crate::{error, Duration, Month, PrimitiveDateTime, Time, Weekday}; + +/// The minimum valid year. +pub(crate) const MIN_YEAR: i32 = if cfg!(feature = "large-dates") { + -999_999 +} else { + -9999 +}; +/// The maximum valid year. +pub(crate) const MAX_YEAR: i32 = if cfg!(feature = "large-dates") { + 999_999 +} else { + 9999 +}; + +/// Date in the proleptic Gregorian calendar. +/// +/// By default, years between ±9999 inclusive are representable. This can be expanded to ±999,999 +/// inclusive by enabling the `large-dates` crate feature. Doing so has performance implications +/// and introduces some ambiguities when parsing. +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Date { + /// Bitpacked field containing both the year and ordinal. + // | xx | xxxxxxxxxxxxxxxxxxxxx | xxxxxxxxx | + // | 2 bits | 21 bits | 9 bits | + // | unassigned | year | ordinal | + // The year is 15 bits when `large-dates` is not enabled. + value: i32, +} + +impl Date { + /// The minimum valid `Date`. + /// + /// The value of this may vary depending on the feature flags enabled. + pub const MIN: Self = Self::__from_ordinal_date_unchecked(MIN_YEAR, 1); + + /// The maximum valid `Date`. + /// + /// The value of this may vary depending on the feature flags enabled. + pub const MAX: Self = Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR)); + + // region: constructors + /// Construct a `Date` from the year and ordinal values, the validity of which must be + /// guaranteed by the caller. + #[doc(hidden)] + pub const fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self { + debug_assert!(year >= MIN_YEAR); + debug_assert!(year <= MAX_YEAR); + debug_assert!(ordinal != 0); + debug_assert!(ordinal <= days_in_year(year)); + + Self { + value: (year << 9) | ordinal as i32, + } + } + + /// Attempt to create a `Date` from the year, month, and day. + /// + /// ```rust + /// # use time::{Date, Month}; + /// assert!(Date::from_calendar_date(2019, Month::January, 1).is_ok()); + /// assert!(Date::from_calendar_date(2019, Month::December, 31).is_ok()); + /// ``` + /// + /// ```rust + /// # use time::{Date, Month}; + /// assert!(Date::from_calendar_date(2019, Month::February, 29).is_err()); // 2019 isn't a leap year. + /// ``` + pub const fn from_calendar_date( + year: i32, + month: Month, + day: u8, + ) -> Result<Self, error::ComponentRange> { + /// Cumulative days through the beginning of a month in both common and leap years. + const DAYS_CUMULATIVE_COMMON_LEAP: [[u16; 12]; 2] = [ + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335], + ]; + + ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); + ensure_value_in_range!(day conditionally in 1 => days_in_year_month(year, month)); + + Ok(Self::__from_ordinal_date_unchecked( + year, + DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1] + + day as u16, + )) + } + + /// Attempt to create a `Date` from the year and ordinal day number. + /// + /// ```rust + /// # use time::Date; + /// assert!(Date::from_ordinal_date(2019, 1).is_ok()); + /// assert!(Date::from_ordinal_date(2019, 365).is_ok()); + /// ``` + /// + /// ```rust + /// # use time::Date; + /// assert!(Date::from_ordinal_date(2019, 366).is_err()); // 2019 isn't a leap year. + /// ``` + pub const fn from_ordinal_date(year: i32, ordinal: u16) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); + ensure_value_in_range!(ordinal conditionally in 1 => days_in_year(year)); + Ok(Self::__from_ordinal_date_unchecked(year, ordinal)) + } + + /// Attempt to create a `Date` from the ISO year, week, and weekday. + /// + /// ```rust + /// # use time::{Date, Weekday::*}; + /// assert!(Date::from_iso_week_date(2019, 1, Monday).is_ok()); + /// assert!(Date::from_iso_week_date(2019, 1, Tuesday).is_ok()); + /// assert!(Date::from_iso_week_date(2020, 53, Friday).is_ok()); + /// ``` + /// + /// ```rust + /// # use time::{Date, Weekday::*}; + /// assert!(Date::from_iso_week_date(2019, 53, Monday).is_err()); // 2019 doesn't have 53 weeks. + /// ``` + pub const fn from_iso_week_date( + year: i32, + week: u8, + weekday: Weekday, + ) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); + ensure_value_in_range!(week conditionally in 1 => weeks_in_year(year)); + + let adj_year = year - 1; + let raw = 365 * adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100) + + div_floor!(adj_year, 400); + let jan_4 = match (raw % 7) as i8 { + -6 | 1 => 8, + -5 | 2 => 9, + -4 | 3 => 10, + -3 | 4 => 4, + -2 | 5 => 5, + -1 | 6 => 6, + _ => 7, + }; + let ordinal = week as i16 * 7 + weekday.number_from_monday() as i16 - jan_4; + + Ok(if ordinal <= 0 { + Self::__from_ordinal_date_unchecked( + year - 1, + (ordinal as u16).wrapping_add(days_in_year(year - 1)), + ) + } else if ordinal > days_in_year(year) as i16 { + Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year)) + } else { + Self::__from_ordinal_date_unchecked(year, ordinal as _) + }) + } + + /// Create a `Date` from the Julian day. + /// + /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is + /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). + /// + /// ```rust + /// # use time::Date; + /// # use time_macros::date; + /// assert_eq!(Date::from_julian_day(0), Ok(date!(-4713 - 11 - 24))); + /// assert_eq!(Date::from_julian_day(2_451_545), Ok(date!(2000 - 01 - 01))); + /// assert_eq!(Date::from_julian_day(2_458_485), Ok(date!(2019 - 01 - 01))); + /// assert_eq!(Date::from_julian_day(2_458_849), Ok(date!(2019 - 12 - 31))); + /// ``` + #[doc(alias = "from_julian_date")] + pub const fn from_julian_day(julian_day: i32) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!( + julian_day in Self::MIN.to_julian_day() => Self::MAX.to_julian_day() + ); + Ok(Self::from_julian_day_unchecked(julian_day)) + } + + /// Create a `Date` from the Julian day. + /// + /// This does not check the validity of the provided Julian day, and as such may result in an + /// internally invalid value. + #[doc(alias = "from_julian_date_unchecked")] + pub(crate) const fn from_julian_day_unchecked(julian_day: i32) -> Self { + debug_assert!(julian_day >= Self::MIN.to_julian_day()); + debug_assert!(julian_day <= Self::MAX.to_julian_day()); + + // To avoid a potential overflow, the value may need to be widened for some arithmetic. + + let z = julian_day - 1_721_119; + let (mut year, mut ordinal) = if julian_day < -19_752_948 || julian_day > 23_195_514 { + let g = 100 * z as i64 - 25; + let a = (g / 3_652_425) as i32; + let b = a - a / 4; + let year = div_floor!(100 * b as i64 + g, 36525) as i32; + let ordinal = (b + z - div_floor!(36525 * year as i64, 100) as i32) as _; + (year, ordinal) + } else { + let g = 100 * z - 25; + let a = g / 3_652_425; + let b = a - a / 4; + let year = div_floor!(100 * b + g, 36525); + let ordinal = (b + z - div_floor!(36525 * year, 100)) as _; + (year, ordinal) + }; + + if is_leap_year(year) { + ordinal += 60; + cascade!(ordinal in 1..367 => year); + } else { + ordinal += 59; + cascade!(ordinal in 1..366 => year); + } + + Self::__from_ordinal_date_unchecked(year, ordinal) + } + // endregion constructors + + // region: getters + /// Get the year of the date. + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).year(), 2019); + /// assert_eq!(date!(2019 - 12 - 31).year(), 2019); + /// assert_eq!(date!(2020 - 01 - 01).year(), 2020); + /// ``` + pub const fn year(self) -> i32 { + self.value >> 9 + } + + /// Get the month. + /// + /// ```rust + /// # use time::Month; + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).month(), Month::January); + /// assert_eq!(date!(2019 - 12 - 31).month(), Month::December); + /// ``` + pub const fn month(self) -> Month { + self.month_day().0 + } + + /// Get the day of the month. + /// + /// The returned value will always be in the range `1..=31`. + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).day(), 1); + /// assert_eq!(date!(2019 - 12 - 31).day(), 31); + /// ``` + pub const fn day(self) -> u8 { + self.month_day().1 + } + + /// Get the month and day. This is more efficient than fetching the components individually. + // For whatever reason, rustc has difficulty optimizing this function. It's significantly faster + // to write the statements out by hand. + pub(crate) const fn month_day(self) -> (Month, u8) { + /// The number of days up to and including the given month. Common years + /// are first, followed by leap years. + const CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP: [[u16; 11]; 2] = [ + [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], + [31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335], + ]; + + let days = CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP[is_leap_year(self.year()) as usize]; + let ordinal = self.ordinal(); + + if ordinal > days[10] { + (Month::December, (ordinal - days[10]) as _) + } else if ordinal > days[9] { + (Month::November, (ordinal - days[9]) as _) + } else if ordinal > days[8] { + (Month::October, (ordinal - days[8]) as _) + } else if ordinal > days[7] { + (Month::September, (ordinal - days[7]) as _) + } else if ordinal > days[6] { + (Month::August, (ordinal - days[6]) as _) + } else if ordinal > days[5] { + (Month::July, (ordinal - days[5]) as _) + } else if ordinal > days[4] { + (Month::June, (ordinal - days[4]) as _) + } else if ordinal > days[3] { + (Month::May, (ordinal - days[3]) as _) + } else if ordinal > days[2] { + (Month::April, (ordinal - days[2]) as _) + } else if ordinal > days[1] { + (Month::March, (ordinal - days[1]) as _) + } else if ordinal > days[0] { + (Month::February, (ordinal - days[0]) as _) + } else { + (Month::January, ordinal as _) + } + } + + /// Get the day of the year. + /// + /// The returned value will always be in the range `1..=366` (`1..=365` for common years). + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).ordinal(), 1); + /// assert_eq!(date!(2019 - 12 - 31).ordinal(), 365); + /// ``` + pub const fn ordinal(self) -> u16 { + (self.value & 0x1FF) as _ + } + + /// Get the ISO 8601 year and week number. + pub(crate) const fn iso_year_week(self) -> (i32, u8) { + let (year, ordinal) = self.to_ordinal_date(); + + match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ { + 0 => (year - 1, weeks_in_year(year - 1)), + 53 if weeks_in_year(year) == 52 => (year + 1, 1), + week => (year, week), + } + } + + /// Get the ISO week number. + /// + /// The returned value will always be in the range `1..=53`. + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).iso_week(), 1); + /// assert_eq!(date!(2019 - 10 - 04).iso_week(), 40); + /// assert_eq!(date!(2020 - 01 - 01).iso_week(), 1); + /// assert_eq!(date!(2020 - 12 - 31).iso_week(), 53); + /// assert_eq!(date!(2021 - 01 - 01).iso_week(), 53); + /// ``` + pub const fn iso_week(self) -> u8 { + self.iso_year_week().1 + } + + /// Get the week number where week 1 begins on the first Sunday. + /// + /// The returned value will always be in the range `0..=53`. + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).sunday_based_week(), 0); + /// assert_eq!(date!(2020 - 01 - 01).sunday_based_week(), 0); + /// assert_eq!(date!(2020 - 12 - 31).sunday_based_week(), 52); + /// assert_eq!(date!(2021 - 01 - 01).sunday_based_week(), 0); + /// ``` + pub const fn sunday_based_week(self) -> u8 { + ((self.ordinal() as i16 - self.weekday().number_days_from_sunday() as i16 + 6) / 7) as _ + } + + /// Get the week number where week 1 begins on the first Monday. + /// + /// The returned value will always be in the range `0..=53`. + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).monday_based_week(), 0); + /// assert_eq!(date!(2020 - 01 - 01).monday_based_week(), 0); + /// assert_eq!(date!(2020 - 12 - 31).monday_based_week(), 52); + /// assert_eq!(date!(2021 - 01 - 01).monday_based_week(), 0); + /// ``` + pub const fn monday_based_week(self) -> u8 { + ((self.ordinal() as i16 - self.weekday().number_days_from_monday() as i16 + 6) / 7) as _ + } + + /// Get the year, month, and day. + /// + /// ```rust + /// # use time::Month; + /// # use time_macros::date; + /// assert_eq!( + /// date!(2019 - 01 - 01).to_calendar_date(), + /// (2019, Month::January, 1) + /// ); + /// ``` + pub const fn to_calendar_date(self) -> (i32, Month, u8) { + let (month, day) = self.month_day(); + (self.year(), month, day) + } + + /// Get the year and ordinal day number. + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).to_ordinal_date(), (2019, 1)); + /// ``` + pub const fn to_ordinal_date(self) -> (i32, u16) { + (self.year(), self.ordinal()) + } + + /// Get the ISO 8601 year, week number, and weekday. + /// + /// ```rust + /// # use time::Weekday::*; + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).to_iso_week_date(), (2019, 1, Tuesday)); + /// assert_eq!(date!(2019 - 10 - 04).to_iso_week_date(), (2019, 40, Friday)); + /// assert_eq!( + /// date!(2020 - 01 - 01).to_iso_week_date(), + /// (2020, 1, Wednesday) + /// ); + /// assert_eq!( + /// date!(2020 - 12 - 31).to_iso_week_date(), + /// (2020, 53, Thursday) + /// ); + /// assert_eq!(date!(2021 - 01 - 01).to_iso_week_date(), (2020, 53, Friday)); + /// ``` + pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { + let (year, ordinal) = self.to_ordinal_date(); + let weekday = self.weekday(); + + match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ { + 0 => (year - 1, weeks_in_year(year - 1), weekday), + 53 if weeks_in_year(year) == 52 => (year + 1, 1, weekday), + week => (year, week, weekday), + } + } + + /// Get the weekday. + /// + /// ```rust + /// # use time::Weekday::*; + /// # use time_macros::date; + /// assert_eq!(date!(2019 - 01 - 01).weekday(), Tuesday); + /// assert_eq!(date!(2019 - 02 - 01).weekday(), Friday); + /// assert_eq!(date!(2019 - 03 - 01).weekday(), Friday); + /// assert_eq!(date!(2019 - 04 - 01).weekday(), Monday); + /// assert_eq!(date!(2019 - 05 - 01).weekday(), Wednesday); + /// assert_eq!(date!(2019 - 06 - 01).weekday(), Saturday); + /// assert_eq!(date!(2019 - 07 - 01).weekday(), Monday); + /// assert_eq!(date!(2019 - 08 - 01).weekday(), Thursday); + /// assert_eq!(date!(2019 - 09 - 01).weekday(), Sunday); + /// assert_eq!(date!(2019 - 10 - 01).weekday(), Tuesday); + /// assert_eq!(date!(2019 - 11 - 01).weekday(), Friday); + /// assert_eq!(date!(2019 - 12 - 01).weekday(), Sunday); + /// ``` + pub const fn weekday(self) -> Weekday { + match self.to_julian_day() % 7 { + -6 | 1 => Weekday::Tuesday, + -5 | 2 => Weekday::Wednesday, + -4 | 3 => Weekday::Thursday, + -3 | 4 => Weekday::Friday, + -2 | 5 => Weekday::Saturday, + -1 | 6 => Weekday::Sunday, + val => { + debug_assert!(val == 0); + Weekday::Monday + } + } + } + + /// Get the next calendar date. + /// + /// ```rust + /// # use time::Date; + /// # use time_macros::date; + /// assert_eq!( + /// date!(2019 - 01 - 01).next_day(), + /// Some(date!(2019 - 01 - 02)) + /// ); + /// assert_eq!( + /// date!(2019 - 01 - 31).next_day(), + /// Some(date!(2019 - 02 - 01)) + /// ); + /// assert_eq!( + /// date!(2019 - 12 - 31).next_day(), + /// Some(date!(2020 - 01 - 01)) + /// ); + /// assert_eq!(Date::MAX.next_day(), None); + /// ``` + pub const fn next_day(self) -> Option<Self> { + if self.ordinal() == 366 || (self.ordinal() == 365 && !is_leap_year(self.year())) { + if self.value == Self::MAX.value { + None + } else { + Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1)) + } + } else { + Some(Self { + value: self.value + 1, + }) + } + } + + /// Get the previous calendar date. + /// + /// ```rust + /// # use time::Date; + /// # use time_macros::date; + /// assert_eq!( + /// date!(2019 - 01 - 02).previous_day(), + /// Some(date!(2019 - 01 - 01)) + /// ); + /// assert_eq!( + /// date!(2019 - 02 - 01).previous_day(), + /// Some(date!(2019 - 01 - 31)) + /// ); + /// assert_eq!( + /// date!(2020 - 01 - 01).previous_day(), + /// Some(date!(2019 - 12 - 31)) + /// ); + /// assert_eq!(Date::MIN.previous_day(), None); + /// ``` + pub const fn previous_day(self) -> Option<Self> { + if self.ordinal() != 1 { + Some(Self { + value: self.value - 1, + }) + } else if self.value == Self::MIN.value { + None + } else { + Some(Self::__from_ordinal_date_unchecked( + self.year() - 1, + days_in_year(self.year() - 1), + )) + } + } + + /// Get the Julian day for the date. + /// + /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is + /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!(date!(-4713 - 11 - 24).to_julian_day(), 0); + /// assert_eq!(date!(2000 - 01 - 01).to_julian_day(), 2_451_545); + /// assert_eq!(date!(2019 - 01 - 01).to_julian_day(), 2_458_485); + /// assert_eq!(date!(2019 - 12 - 31).to_julian_day(), 2_458_849); + /// ``` + pub const fn to_julian_day(self) -> i32 { + let year = self.year() - 1; + let ordinal = self.ordinal() as i32; + + ordinal + 365 * year + div_floor!(year, 4) - div_floor!(year, 100) + + div_floor!(year, 400) + + 1_721_425 + } + // endregion getters + + // region: checked arithmetic + /// Computes `self + duration`, returning `None` if an overflow occurred. + /// + /// ```rust + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MAX.checked_add(1.days()), None); + /// assert_eq!(Date::MIN.checked_add((-2).days()), None); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_add(2.days()), + /// Some(date!(2021 - 01 - 02)) + /// ); + /// ``` + /// + /// # Note + /// + /// This function only takes whole days into account. + /// + /// ```rust + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MAX.checked_add(23.hours()), Some(Date::MAX)); + /// assert_eq!(Date::MIN.checked_add((-23).hours()), Some(Date::MIN)); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_add(23.hours()), + /// Some(date!(2020 - 12 - 31)) + /// ); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_add(47.hours()), + /// Some(date!(2021 - 01 - 01)) + /// ); + /// ``` + pub const fn checked_add(self, duration: Duration) -> Option<Self> { + let whole_days = duration.whole_days(); + if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 { + return None; + } + + let julian_day = const_try_opt!(self.to_julian_day().checked_add(whole_days as _)); + if let Ok(date) = Self::from_julian_day(julian_day) { + Some(date) + } else { + None + } + } + + /// Computes `self - duration`, returning `None` if an overflow occurred. + /// + /// ``` + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MAX.checked_sub((-2).days()), None); + /// assert_eq!(Date::MIN.checked_sub(1.days()), None); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_sub(2.days()), + /// Some(date!(2020 - 12 - 29)) + /// ); + /// ``` + /// + /// # Note + /// + /// This function only takes whole days into account. + /// + /// ``` + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MAX.checked_sub((-23).hours()), Some(Date::MAX)); + /// assert_eq!(Date::MIN.checked_sub(23.hours()), Some(Date::MIN)); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_sub(23.hours()), + /// Some(date!(2020 - 12 - 31)) + /// ); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_sub(47.hours()), + /// Some(date!(2020 - 12 - 30)) + /// ); + /// ``` + pub const fn checked_sub(self, duration: Duration) -> Option<Self> { + let whole_days = duration.whole_days(); + if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 { + return None; + } + + let julian_day = const_try_opt!(self.to_julian_day().checked_sub(whole_days as _)); + if let Ok(date) = Self::from_julian_day(julian_day) { + Some(date) + } else { + None + } + } + // endregion: checked arithmetic + + // region: saturating arithmetic + /// Computes `self + duration`, saturating value on overflow. + /// + /// ```rust + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MAX.saturating_add(1.days()), Date::MAX); + /// assert_eq!(Date::MIN.saturating_add((-2).days()), Date::MIN); + /// assert_eq!( + /// date!(2020 - 12 - 31).saturating_add(2.days()), + /// date!(2021 - 01 - 02) + /// ); + /// ``` + /// + /// # Note + /// + /// This function only takes whole days into account. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// # use time_macros::date; + /// assert_eq!( + /// date!(2020 - 12 - 31).saturating_add(23.hours()), + /// date!(2020 - 12 - 31) + /// ); + /// assert_eq!( + /// date!(2020 - 12 - 31).saturating_add(47.hours()), + /// date!(2021 - 01 - 01) + /// ); + /// ``` + pub const fn saturating_add(self, duration: Duration) -> Self { + if let Some(datetime) = self.checked_add(duration) { + datetime + } else if duration.is_negative() { + Self::MIN + } else { + debug_assert!(duration.is_positive()); + Self::MAX + } + } + + /// Computes `self - duration`, saturating value on overflow. + /// + /// ``` + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MAX.saturating_sub((-2).days()), Date::MAX); + /// assert_eq!(Date::MIN.saturating_sub(1.days()), Date::MIN); + /// assert_eq!( + /// date!(2020 - 12 - 31).saturating_sub(2.days()), + /// date!(2020 - 12 - 29) + /// ); + /// ``` + /// + /// # Note + /// + /// This function only takes whole days into account. + /// + /// ``` + /// # use time::ext::NumericalDuration; + /// # use time_macros::date; + /// assert_eq!( + /// date!(2020 - 12 - 31).saturating_sub(23.hours()), + /// date!(2020 - 12 - 31) + /// ); + /// assert_eq!( + /// date!(2020 - 12 - 31).saturating_sub(47.hours()), + /// date!(2020 - 12 - 30) + /// ); + /// ``` + pub const fn saturating_sub(self, duration: Duration) -> Self { + if let Some(datetime) = self.checked_sub(duration) { + datetime + } else if duration.is_negative() { + Self::MAX + } else { + debug_assert!(duration.is_positive()); + Self::MIN + } + } + // region: saturating arithmetic + + // region: replacement + /// Replace the year. The month and day will be unchanged. + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!( + /// date!(2022 - 02 - 18).replace_year(2019), + /// Ok(date!(2019 - 02 - 18)) + /// ); + /// assert!(date!(2022 - 02 - 18).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year + /// assert!(date!(2022 - 02 - 18).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year + /// ``` + #[must_use = "This method does not mutate the original `Date`."] + pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); + + let ordinal = self.ordinal(); + + // Dates in January and February are unaffected by leap years. + if ordinal <= 59 { + return Ok(Self::__from_ordinal_date_unchecked(year, ordinal)); + } + + match (is_leap_year(self.year()), is_leap_year(year)) { + (false, false) | (true, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal)), + // February 29 does not exist in common years. + (true, false) if ordinal == 60 => Err(error::ComponentRange { + name: "day", + value: 29, + minimum: 1, + maximum: 28, + conditional_range: true, + }), + // We're going from a common year to a leap year. Shift dates in March and later by + // one day. + (false, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal + 1)), + // We're going from a leap year to a common year. Shift dates in January and + // February by one day. + (true, false) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal - 1)), + } + } + + /// Replace the month of the year. + /// + /// ```rust + /// # use time_macros::date; + /// # use time::Month; + /// assert_eq!( + /// date!(2022 - 02 - 18).replace_month(Month::January), + /// Ok(date!(2022 - 01 - 18)) + /// ); + /// assert!( + /// date!(2022 - 01 - 30) + /// .replace_month(Month::February) + /// .is_err() + /// ); // 30 isn't a valid day in February + /// ``` + #[must_use = "This method does not mutate the original `Date`."] + pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> { + let (year, _, day) = self.to_calendar_date(); + Self::from_calendar_date(year, month, day) + } + + /// Replace the day of the month. + /// + /// ```rust + /// # use time_macros::date; + /// assert_eq!( + /// date!(2022 - 02 - 18).replace_day(1), + /// Ok(date!(2022 - 02 - 01)) + /// ); + /// assert!(date!(2022 - 02 - 18).replace_day(0).is_err()); // 0 isn't a valid day + /// assert!(date!(2022 - 02 - 18).replace_day(30).is_err()); // 30 isn't a valid day in February + /// ``` + #[must_use = "This method does not mutate the original `Date`."] + pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> { + // Days 1-28 are present in every month, so we can skip checking. + if day == 0 || day >= 29 { + ensure_value_in_range!( + day conditionally in 1 => days_in_year_month(self.year(), self.month()) + ); + } + + Ok(Self::__from_ordinal_date_unchecked( + self.year(), + (self.ordinal() as i16 - self.day() as i16 + day as i16) as _, + )) + } + // endregion replacement +} + +// region: attach time +/// Methods to add a [`Time`] component, resulting in a [`PrimitiveDateTime`]. +impl Date { + /// Create a [`PrimitiveDateTime`] using the existing date. The [`Time`] component will be set + /// to midnight. + /// + /// ```rust + /// # use time_macros::{date, datetime}; + /// assert_eq!(date!(1970-01-01).midnight(), datetime!(1970-01-01 0:00)); + /// ``` + pub const fn midnight(self) -> PrimitiveDateTime { + PrimitiveDateTime::new(self, Time::MIDNIGHT) + } + + /// Create a [`PrimitiveDateTime`] using the existing date and the provided [`Time`]. + /// + /// ```rust + /// # use time_macros::{date, datetime, time}; + /// assert_eq!( + /// date!(1970-01-01).with_time(time!(0:00)), + /// datetime!(1970-01-01 0:00), + /// ); + /// ``` + pub const fn with_time(self, time: Time) -> PrimitiveDateTime { + PrimitiveDateTime::new(self, time) + } + + /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. + /// + /// ```rust + /// # use time_macros::date; + /// assert!(date!(1970 - 01 - 01).with_hms(0, 0, 0).is_ok()); + /// assert!(date!(1970 - 01 - 01).with_hms(24, 0, 0).is_err()); + /// ``` + pub const fn with_hms( + self, + hour: u8, + minute: u8, + second: u8, + ) -> Result<PrimitiveDateTime, error::ComponentRange> { + Ok(PrimitiveDateTime::new( + self, + const_try!(Time::from_hms(hour, minute, second)), + )) + } + + /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. + /// + /// ```rust + /// # use time_macros::date; + /// assert!(date!(1970 - 01 - 01).with_hms_milli(0, 0, 0, 0).is_ok()); + /// assert!(date!(1970 - 01 - 01).with_hms_milli(24, 0, 0, 0).is_err()); + /// ``` + pub const fn with_hms_milli( + self, + hour: u8, + minute: u8, + second: u8, + millisecond: u16, + ) -> Result<PrimitiveDateTime, error::ComponentRange> { + Ok(PrimitiveDateTime::new( + self, + const_try!(Time::from_hms_milli(hour, minute, second, millisecond)), + )) + } + + /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. + /// + /// ```rust + /// # use time_macros::date; + /// assert!(date!(1970 - 01 - 01).with_hms_micro(0, 0, 0, 0).is_ok()); + /// assert!(date!(1970 - 01 - 01).with_hms_micro(24, 0, 0, 0).is_err()); + /// ``` + pub const fn with_hms_micro( + self, + hour: u8, + minute: u8, + second: u8, + microsecond: u32, + ) -> Result<PrimitiveDateTime, error::ComponentRange> { + Ok(PrimitiveDateTime::new( + self, + const_try!(Time::from_hms_micro(hour, minute, second, microsecond)), + )) + } + + /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. + /// + /// ```rust + /// # use time_macros::date; + /// assert!(date!(1970 - 01 - 01).with_hms_nano(0, 0, 0, 0).is_ok()); + /// assert!(date!(1970 - 01 - 01).with_hms_nano(24, 0, 0, 0).is_err()); + /// ``` + pub const fn with_hms_nano( + self, + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + ) -> Result<PrimitiveDateTime, error::ComponentRange> { + Ok(PrimitiveDateTime::new( + self, + const_try!(Time::from_hms_nano(hour, minute, second, nanosecond)), + )) + } +} +// endregion attach time + +// region: formatting & parsing +#[cfg(feature = "formatting")] +impl Date { + /// Format the `Date` using the provided [format description](crate::format_description). + pub fn format_into( + self, + output: &mut impl io::Write, + format: &(impl Formattable + ?Sized), + ) -> Result<usize, error::Format> { + format.format_into(output, Some(self), None, None) + } + + /// Format the `Date` using the provided [format description](crate::format_description). + /// + /// ```rust + /// # use time::{format_description}; + /// # use time_macros::date; + /// let format = format_description::parse("[year]-[month]-[day]")?; + /// assert_eq!(date!(2020 - 01 - 02).format(&format)?, "2020-01-02"); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { + format.format(Some(self), None, None) + } +} + +#[cfg(feature = "parsing")] +impl Date { + /// Parse a `Date` from the input using the provided [format + /// description](crate::format_description). + /// + /// ```rust + /// # use time::Date; + /// # use time_macros::{date, format_description}; + /// let format = format_description!("[year]-[month]-[day]"); + /// assert_eq!(Date::parse("2020-01-02", &format)?, date!(2020 - 01 - 02)); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn parse( + input: &str, + description: &(impl Parsable + ?Sized), + ) -> Result<Self, error::Parse> { + description.parse_date(input.as_bytes()) + } +} + +impl fmt::Display for Date { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if cfg!(feature = "large-dates") && self.year().abs() >= 10_000 { + write!( + f, + "{:+}-{:02}-{:02}", + self.year(), + self.month() as u8, + self.day() + ) + } else { + write!( + f, + "{:0width$}-{:02}-{:02}", + self.year(), + self.month() as u8, + self.day(), + width = 4 + (self.year() < 0) as usize + ) + } + } +} + +impl fmt::Debug for Date { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Display::fmt(self, f) + } +} +// endregion formatting & parsing + +// region: trait impls +impl Add<Duration> for Date { + type Output = Self; + + fn add(self, duration: Duration) -> Self::Output { + self.checked_add(duration) + .expect("overflow adding duration to date") + } +} + +impl Add<StdDuration> for Date { + type Output = Self; + + fn add(self, duration: StdDuration) -> Self::Output { + Self::from_julian_day(self.to_julian_day() + (duration.as_secs() / 86_400) as i32) + .expect("overflow adding duration to date") + } +} + +impl_add_assign!(Date: Duration, StdDuration); + +impl Sub<Duration> for Date { + type Output = Self; + + fn sub(self, duration: Duration) -> Self::Output { + self.checked_sub(duration) + .expect("overflow subtracting duration from date") + } +} + +impl Sub<StdDuration> for Date { + type Output = Self; + + fn sub(self, duration: StdDuration) -> Self::Output { + Self::from_julian_day(self.to_julian_day() - (duration.as_secs() / 86_400) as i32) + .expect("overflow subtracting duration from date") + } +} + +impl_sub_assign!(Date: Duration, StdDuration); + +impl Sub for Date { + type Output = Duration; + + fn sub(self, other: Self) -> Self::Output { + Duration::days((self.to_julian_day() - other.to_julian_day()) as _) + } +} +// endregion trait impls diff --git a/third_party/rust/time/src/duration.rs b/third_party/rust/time/src/duration.rs new file mode 100644 index 0000000000..f8d916f451 --- /dev/null +++ b/third_party/rust/time/src/duration.rs @@ -0,0 +1,1139 @@ +//! The [`Duration`] struct and its associated `impl`s. + +use core::cmp::Ordering; +use core::fmt; +use core::iter::Sum; +use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; +use core::time::Duration as StdDuration; + +use crate::error; +#[cfg(feature = "std")] +use crate::Instant; + +/// By explicitly inserting this enum where padding is expected, the compiler is able to better +/// perform niche value optimization. +#[repr(u32)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub(crate) enum Padding { + #[allow(clippy::missing_docs_in_private_items)] + Optimize, +} + +impl Default for Padding { + fn default() -> Self { + Self::Optimize + } +} + +/// A span of time with nanosecond precision. +/// +/// Each `Duration` is composed of a whole number of seconds and a fractional part represented in +/// nanoseconds. +/// +/// This implementation allows for negative durations, unlike [`core::time::Duration`]. +#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Duration { + /// Number of whole seconds. + seconds: i64, + /// Number of nanoseconds within the second. The sign always matches the `seconds` field. + nanoseconds: i32, // always -10^9 < nanoseconds < 10^9 + #[allow(clippy::missing_docs_in_private_items)] + padding: Padding, +} + +impl fmt::Debug for Duration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Duration") + .field("seconds", &self.seconds) + .field("nanoseconds", &self.nanoseconds) + .finish() + } +} + +impl Duration { + // region: constants + /// Equivalent to `0.seconds()`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::ZERO, 0.seconds()); + /// ``` + pub const ZERO: Self = Self::seconds(0); + + /// Equivalent to `1.nanoseconds()`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::NANOSECOND, 1.nanoseconds()); + /// ``` + pub const NANOSECOND: Self = Self::nanoseconds(1); + + /// Equivalent to `1.microseconds()`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::MICROSECOND, 1.microseconds()); + /// ``` + pub const MICROSECOND: Self = Self::microseconds(1); + + /// Equivalent to `1.milliseconds()`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::MILLISECOND, 1.milliseconds()); + /// ``` + pub const MILLISECOND: Self = Self::milliseconds(1); + + /// Equivalent to `1.seconds()`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::SECOND, 1.seconds()); + /// ``` + pub const SECOND: Self = Self::seconds(1); + + /// Equivalent to `1.minutes()`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::MINUTE, 1.minutes()); + /// ``` + pub const MINUTE: Self = Self::minutes(1); + + /// Equivalent to `1.hours()`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::HOUR, 1.hours()); + /// ``` + pub const HOUR: Self = Self::hours(1); + + /// Equivalent to `1.days()`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::DAY, 1.days()); + /// ``` + pub const DAY: Self = Self::days(1); + + /// Equivalent to `1.weeks()`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::WEEK, 1.weeks()); + /// ``` + pub const WEEK: Self = Self::weeks(1); + + /// The minimum possible duration. Adding any negative duration to this will cause an overflow. + pub const MIN: Self = Self::new_unchecked(i64::MIN, -999_999_999); + + /// The maximum possible duration. Adding any positive duration to this will cause an overflow. + pub const MAX: Self = Self::new_unchecked(i64::MAX, 999_999_999); + // endregion constants + + // region: is_{sign} + /// Check if a duration is exactly zero. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert!(0.seconds().is_zero()); + /// assert!(!1.nanoseconds().is_zero()); + /// ``` + pub const fn is_zero(self) -> bool { + self.seconds == 0 && self.nanoseconds == 0 + } + + /// Check if a duration is negative. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert!((-1).seconds().is_negative()); + /// assert!(!0.seconds().is_negative()); + /// assert!(!1.seconds().is_negative()); + /// ``` + pub const fn is_negative(self) -> bool { + self.seconds < 0 || self.nanoseconds < 0 + } + + /// Check if a duration is positive. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert!(1.seconds().is_positive()); + /// assert!(!0.seconds().is_positive()); + /// assert!(!(-1).seconds().is_positive()); + /// ``` + pub const fn is_positive(self) -> bool { + self.seconds > 0 || self.nanoseconds > 0 + } + // endregion is_{sign} + + // region: abs + /// Get the absolute value of the duration. + /// + /// This method saturates the returned value if it would otherwise overflow. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.seconds().abs(), 1.seconds()); + /// assert_eq!(0.seconds().abs(), 0.seconds()); + /// assert_eq!((-1).seconds().abs(), 1.seconds()); + /// ``` + pub const fn abs(self) -> Self { + Self::new_unchecked(self.seconds.saturating_abs(), self.nanoseconds.abs()) + } + + /// Convert the existing `Duration` to a `std::time::Duration` and its sign. This returns a + /// [`std::time::Duration`] and does not saturate the returned value (unlike [`Duration::abs`]). + /// + /// ```rust + /// # use time::ext::{NumericalDuration, NumericalStdDuration}; + /// assert_eq!(1.seconds().unsigned_abs(), 1.std_seconds()); + /// assert_eq!(0.seconds().unsigned_abs(), 0.std_seconds()); + /// assert_eq!((-1).seconds().unsigned_abs(), 1.std_seconds()); + /// ``` + pub const fn unsigned_abs(self) -> StdDuration { + StdDuration::new(self.seconds.unsigned_abs(), self.nanoseconds.unsigned_abs()) + } + // endregion abs + + // region: constructors + /// Create a new `Duration` without checking the validity of the components. + pub(crate) const fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self { + if seconds < 0 { + debug_assert!(nanoseconds <= 0); + debug_assert!(nanoseconds > -1_000_000_000); + } else if seconds > 0 { + debug_assert!(nanoseconds >= 0); + debug_assert!(nanoseconds < 1_000_000_000); + } else { + debug_assert!(nanoseconds.unsigned_abs() < 1_000_000_000); + } + + Self { + seconds, + nanoseconds, + padding: Padding::Optimize, + } + } + + /// Create a new `Duration` with the provided seconds and nanoseconds. If nanoseconds is at + /// least ±10<sup>9</sup>, it will wrap to the number of seconds. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::new(1, 0), 1.seconds()); + /// assert_eq!(Duration::new(-1, 0), (-1).seconds()); + /// assert_eq!(Duration::new(1, 2_000_000_000), 3.seconds()); + /// ``` + pub const fn new(mut seconds: i64, mut nanoseconds: i32) -> Self { + seconds = expect_opt!( + seconds.checked_add(nanoseconds as i64 / 1_000_000_000), + "overflow constructing `time::Duration`" + ); + nanoseconds %= 1_000_000_000; + + if seconds > 0 && nanoseconds < 0 { + // `seconds` cannot overflow here because it is positive. + seconds -= 1; + nanoseconds += 1_000_000_000; + } else if seconds < 0 && nanoseconds > 0 { + // `seconds` cannot overflow here because it is negative. + seconds += 1; + nanoseconds -= 1_000_000_000; + } + + Self::new_unchecked(seconds, nanoseconds) + } + + /// Create a new `Duration` with the given number of weeks. Equivalent to + /// `Duration::seconds(weeks * 604_800)`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::weeks(1), 604_800.seconds()); + /// ``` + pub const fn weeks(weeks: i64) -> Self { + Self::seconds(expect_opt!( + weeks.checked_mul(604_800), + "overflow constructing `time::Duration`" + )) + } + + /// Create a new `Duration` with the given number of days. Equivalent to + /// `Duration::seconds(days * 86_400)`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::days(1), 86_400.seconds()); + /// ``` + pub const fn days(days: i64) -> Self { + Self::seconds(expect_opt!( + days.checked_mul(86_400), + "overflow constructing `time::Duration`" + )) + } + + /// Create a new `Duration` with the given number of hours. Equivalent to + /// `Duration::seconds(hours * 3_600)`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::hours(1), 3_600.seconds()); + /// ``` + pub const fn hours(hours: i64) -> Self { + Self::seconds(expect_opt!( + hours.checked_mul(3_600), + "overflow constructing `time::Duration`" + )) + } + + /// Create a new `Duration` with the given number of minutes. Equivalent to + /// `Duration::seconds(minutes * 60)`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::minutes(1), 60.seconds()); + /// ``` + pub const fn minutes(minutes: i64) -> Self { + Self::seconds(expect_opt!( + minutes.checked_mul(60), + "overflow constructing `time::Duration`" + )) + } + + /// Create a new `Duration` with the given number of seconds. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::seconds(1), 1_000.milliseconds()); + /// ``` + pub const fn seconds(seconds: i64) -> Self { + Self::new_unchecked(seconds, 0) + } + + /// Creates a new `Duration` from the specified number of seconds represented as `f64`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::seconds_f64(0.5), 0.5.seconds()); + /// assert_eq!(Duration::seconds_f64(-0.5), -0.5.seconds()); + /// ``` + pub fn seconds_f64(seconds: f64) -> Self { + if seconds > i64::MAX as f64 || seconds < i64::MIN as f64 { + crate::expect_failed("overflow constructing `time::Duration`"); + } + if seconds.is_nan() { + crate::expect_failed("passed NaN to `time::Duration::seconds_f64`"); + } + Self::new_unchecked(seconds as _, ((seconds % 1.) * 1_000_000_000.) as _) + } + + /// Creates a new `Duration` from the specified number of seconds represented as `f32`. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::seconds_f32(0.5), 0.5.seconds()); + /// assert_eq!(Duration::seconds_f32(-0.5), (-0.5).seconds()); + /// ``` + pub fn seconds_f32(seconds: f32) -> Self { + if seconds > i64::MAX as f32 || seconds < i64::MIN as f32 { + crate::expect_failed("overflow constructing `time::Duration`"); + } + if seconds.is_nan() { + crate::expect_failed("passed NaN to `time::Duration::seconds_f32`"); + } + Self::new_unchecked(seconds as _, ((seconds % 1.) * 1_000_000_000.) as _) + } + + /// Create a new `Duration` with the given number of milliseconds. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::milliseconds(1), 1_000.microseconds()); + /// assert_eq!(Duration::milliseconds(-1), (-1_000).microseconds()); + /// ``` + pub const fn milliseconds(milliseconds: i64) -> Self { + Self::new_unchecked( + milliseconds / 1_000, + ((milliseconds % 1_000) * 1_000_000) as _, + ) + } + + /// Create a new `Duration` with the given number of microseconds. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::microseconds(1), 1_000.nanoseconds()); + /// assert_eq!(Duration::microseconds(-1), (-1_000).nanoseconds()); + /// ``` + pub const fn microseconds(microseconds: i64) -> Self { + Self::new_unchecked( + microseconds / 1_000_000, + ((microseconds % 1_000_000) * 1_000) as _, + ) + } + + /// Create a new `Duration` with the given number of nanoseconds. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(Duration::nanoseconds(1), 1.microseconds() / 1_000); + /// assert_eq!(Duration::nanoseconds(-1), (-1).microseconds() / 1_000); + /// ``` + pub const fn nanoseconds(nanoseconds: i64) -> Self { + Self::new_unchecked( + nanoseconds / 1_000_000_000, + (nanoseconds % 1_000_000_000) as _, + ) + } + + /// Create a new `Duration` with the given number of nanoseconds. + /// + /// As the input range cannot be fully mapped to the output, this should only be used where it's + /// known to result in a valid value. + pub(crate) const fn nanoseconds_i128(nanoseconds: i128) -> Self { + let seconds = nanoseconds / 1_000_000_000; + let nanoseconds = nanoseconds % 1_000_000_000; + + if seconds > i64::MAX as i128 || seconds < i64::MIN as i128 { + crate::expect_failed("overflow constructing `time::Duration`"); + } + + Self::new_unchecked(seconds as _, nanoseconds as _) + } + // endregion constructors + + // region: getters + /// Get the number of whole weeks in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.weeks().whole_weeks(), 1); + /// assert_eq!((-1).weeks().whole_weeks(), -1); + /// assert_eq!(6.days().whole_weeks(), 0); + /// assert_eq!((-6).days().whole_weeks(), 0); + /// ``` + pub const fn whole_weeks(self) -> i64 { + self.whole_seconds() / 604_800 + } + + /// Get the number of whole days in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.days().whole_days(), 1); + /// assert_eq!((-1).days().whole_days(), -1); + /// assert_eq!(23.hours().whole_days(), 0); + /// assert_eq!((-23).hours().whole_days(), 0); + /// ``` + pub const fn whole_days(self) -> i64 { + self.whole_seconds() / 86_400 + } + + /// Get the number of whole hours in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.hours().whole_hours(), 1); + /// assert_eq!((-1).hours().whole_hours(), -1); + /// assert_eq!(59.minutes().whole_hours(), 0); + /// assert_eq!((-59).minutes().whole_hours(), 0); + /// ``` + pub const fn whole_hours(self) -> i64 { + self.whole_seconds() / 3_600 + } + + /// Get the number of whole minutes in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.minutes().whole_minutes(), 1); + /// assert_eq!((-1).minutes().whole_minutes(), -1); + /// assert_eq!(59.seconds().whole_minutes(), 0); + /// assert_eq!((-59).seconds().whole_minutes(), 0); + /// ``` + pub const fn whole_minutes(self) -> i64 { + self.whole_seconds() / 60 + } + + /// Get the number of whole seconds in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.seconds().whole_seconds(), 1); + /// assert_eq!((-1).seconds().whole_seconds(), -1); + /// assert_eq!(1.minutes().whole_seconds(), 60); + /// assert_eq!((-1).minutes().whole_seconds(), -60); + /// ``` + pub const fn whole_seconds(self) -> i64 { + self.seconds + } + + /// Get the number of fractional seconds in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.5.seconds().as_seconds_f64(), 1.5); + /// assert_eq!((-1.5).seconds().as_seconds_f64(), -1.5); + /// ``` + pub fn as_seconds_f64(self) -> f64 { + self.seconds as f64 + self.nanoseconds as f64 / 1_000_000_000. + } + + /// Get the number of fractional seconds in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.5.seconds().as_seconds_f32(), 1.5); + /// assert_eq!((-1.5).seconds().as_seconds_f32(), -1.5); + /// ``` + pub fn as_seconds_f32(self) -> f32 { + self.seconds as f32 + self.nanoseconds as f32 / 1_000_000_000. + } + + /// Get the number of whole milliseconds in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.seconds().whole_milliseconds(), 1_000); + /// assert_eq!((-1).seconds().whole_milliseconds(), -1_000); + /// assert_eq!(1.milliseconds().whole_milliseconds(), 1); + /// assert_eq!((-1).milliseconds().whole_milliseconds(), -1); + /// ``` + pub const fn whole_milliseconds(self) -> i128 { + self.seconds as i128 * 1_000 + self.nanoseconds as i128 / 1_000_000 + } + + /// Get the number of milliseconds past the number of whole seconds. + /// + /// Always in the range `-1_000..1_000`. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.4.seconds().subsec_milliseconds(), 400); + /// assert_eq!((-1.4).seconds().subsec_milliseconds(), -400); + /// ``` + // Allow the lint, as the value is guaranteed to be less than 1000. + pub const fn subsec_milliseconds(self) -> i16 { + (self.nanoseconds / 1_000_000) as _ + } + + /// Get the number of whole microseconds in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.milliseconds().whole_microseconds(), 1_000); + /// assert_eq!((-1).milliseconds().whole_microseconds(), -1_000); + /// assert_eq!(1.microseconds().whole_microseconds(), 1); + /// assert_eq!((-1).microseconds().whole_microseconds(), -1); + /// ``` + pub const fn whole_microseconds(self) -> i128 { + self.seconds as i128 * 1_000_000 + self.nanoseconds as i128 / 1_000 + } + + /// Get the number of microseconds past the number of whole seconds. + /// + /// Always in the range `-1_000_000..1_000_000`. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.0004.seconds().subsec_microseconds(), 400); + /// assert_eq!((-1.0004).seconds().subsec_microseconds(), -400); + /// ``` + pub const fn subsec_microseconds(self) -> i32 { + self.nanoseconds / 1_000 + } + + /// Get the number of nanoseconds in the duration. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.microseconds().whole_nanoseconds(), 1_000); + /// assert_eq!((-1).microseconds().whole_nanoseconds(), -1_000); + /// assert_eq!(1.nanoseconds().whole_nanoseconds(), 1); + /// assert_eq!((-1).nanoseconds().whole_nanoseconds(), -1); + /// ``` + pub const fn whole_nanoseconds(self) -> i128 { + self.seconds as i128 * 1_000_000_000 + self.nanoseconds as i128 + } + + /// Get the number of nanoseconds past the number of whole seconds. + /// + /// The returned value will always be in the range `-1_000_000_000..1_000_000_000`. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(1.000_000_400.seconds().subsec_nanoseconds(), 400); + /// assert_eq!((-1.000_000_400).seconds().subsec_nanoseconds(), -400); + /// ``` + pub const fn subsec_nanoseconds(self) -> i32 { + self.nanoseconds + } + // endregion getters + + // region: checked arithmetic + /// Computes `self + rhs`, returning `None` if an overflow occurred. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(5.seconds().checked_add(5.seconds()), Some(10.seconds())); + /// assert_eq!(Duration::MAX.checked_add(1.nanoseconds()), None); + /// assert_eq!((-5).seconds().checked_add(5.seconds()), Some(0.seconds())); + /// ``` + pub const fn checked_add(self, rhs: Self) -> Option<Self> { + let mut seconds = const_try_opt!(self.seconds.checked_add(rhs.seconds)); + let mut nanoseconds = self.nanoseconds + rhs.nanoseconds; + + if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 { + nanoseconds -= 1_000_000_000; + seconds = const_try_opt!(seconds.checked_add(1)); + } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 { + nanoseconds += 1_000_000_000; + seconds = const_try_opt!(seconds.checked_sub(1)); + } + + Some(Self::new_unchecked(seconds, nanoseconds)) + } + + /// Computes `self - rhs`, returning `None` if an overflow occurred. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(5.seconds().checked_sub(5.seconds()), Some(Duration::ZERO)); + /// assert_eq!(Duration::MIN.checked_sub(1.nanoseconds()), None); + /// assert_eq!(5.seconds().checked_sub(10.seconds()), Some((-5).seconds())); + /// ``` + pub const fn checked_sub(self, rhs: Self) -> Option<Self> { + let mut seconds = const_try_opt!(self.seconds.checked_sub(rhs.seconds)); + let mut nanoseconds = self.nanoseconds - rhs.nanoseconds; + + if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 { + nanoseconds -= 1_000_000_000; + seconds = const_try_opt!(seconds.checked_add(1)); + } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 { + nanoseconds += 1_000_000_000; + seconds = const_try_opt!(seconds.checked_sub(1)); + } + + Some(Self::new_unchecked(seconds, nanoseconds)) + } + + /// Computes `self * rhs`, returning `None` if an overflow occurred. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(5.seconds().checked_mul(2), Some(10.seconds())); + /// assert_eq!(5.seconds().checked_mul(-2), Some((-10).seconds())); + /// assert_eq!(5.seconds().checked_mul(0), Some(0.seconds())); + /// assert_eq!(Duration::MAX.checked_mul(2), None); + /// assert_eq!(Duration::MIN.checked_mul(2), None); + /// ``` + pub const fn checked_mul(self, rhs: i32) -> Option<Self> { + // Multiply nanoseconds as i64, because it cannot overflow that way. + let total_nanos = self.nanoseconds as i64 * rhs as i64; + let extra_secs = total_nanos / 1_000_000_000; + let nanoseconds = (total_nanos % 1_000_000_000) as _; + let seconds = const_try_opt!( + const_try_opt!(self.seconds.checked_mul(rhs as _)).checked_add(extra_secs) + ); + + Some(Self::new_unchecked(seconds, nanoseconds)) + } + + /// Computes `self / rhs`, returning `None` if `rhs == 0` or if the result would overflow. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// assert_eq!(10.seconds().checked_div(2), Some(5.seconds())); + /// assert_eq!(10.seconds().checked_div(-2), Some((-5).seconds())); + /// assert_eq!(1.seconds().checked_div(0), None); + /// ``` + pub const fn checked_div(self, rhs: i32) -> Option<Self> { + let seconds = const_try_opt!(self.seconds.checked_div(rhs as i64)); + let carry = self.seconds - seconds * (rhs as i64); + let extra_nanos = const_try_opt!((carry * 1_000_000_000).checked_div(rhs as i64)); + let nanoseconds = const_try_opt!(self.nanoseconds.checked_div(rhs)) + (extra_nanos as i32); + + Some(Self::new_unchecked(seconds, nanoseconds)) + } + // endregion checked arithmetic + + // region: saturating arithmetic + /// Computes `self + rhs`, saturating if an overflow occurred. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(5.seconds().saturating_add(5.seconds()), 10.seconds()); + /// assert_eq!(Duration::MAX.saturating_add(1.nanoseconds()), Duration::MAX); + /// assert_eq!( + /// Duration::MIN.saturating_add((-1).nanoseconds()), + /// Duration::MIN + /// ); + /// assert_eq!((-5).seconds().saturating_add(5.seconds()), Duration::ZERO); + /// ``` + pub const fn saturating_add(self, rhs: Self) -> Self { + let (mut seconds, overflow) = self.seconds.overflowing_add(rhs.seconds); + if overflow { + if self.seconds > 0 { + return Self::MAX; + } + return Self::MIN; + } + let mut nanoseconds = self.nanoseconds + rhs.nanoseconds; + + if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 { + nanoseconds -= 1_000_000_000; + seconds = match seconds.checked_add(1) { + Some(seconds) => seconds, + None => return Self::MAX, + }; + } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 { + nanoseconds += 1_000_000_000; + seconds = match seconds.checked_sub(1) { + Some(seconds) => seconds, + None => return Self::MIN, + }; + } + + Self::new_unchecked(seconds, nanoseconds) + } + + /// Computes `self - rhs`, saturating if an overflow occurred. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(5.seconds().saturating_sub(5.seconds()), Duration::ZERO); + /// assert_eq!(Duration::MIN.saturating_sub(1.nanoseconds()), Duration::MIN); + /// assert_eq!( + /// Duration::MAX.saturating_sub((-1).nanoseconds()), + /// Duration::MAX + /// ); + /// assert_eq!(5.seconds().saturating_sub(10.seconds()), (-5).seconds()); + /// ``` + pub const fn saturating_sub(self, rhs: Self) -> Self { + let (mut seconds, overflow) = self.seconds.overflowing_sub(rhs.seconds); + if overflow { + if self.seconds > 0 { + return Self::MAX; + } + return Self::MIN; + } + let mut nanoseconds = self.nanoseconds - rhs.nanoseconds; + + if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 { + nanoseconds -= 1_000_000_000; + seconds = match seconds.checked_add(1) { + Some(seconds) => seconds, + None => return Self::MAX, + }; + } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 { + nanoseconds += 1_000_000_000; + seconds = match seconds.checked_sub(1) { + Some(seconds) => seconds, + None => return Self::MIN, + }; + } + + Self::new_unchecked(seconds, nanoseconds) + } + + /// Computes `self * rhs`, saturating if an overflow occurred. + /// + /// ```rust + /// # use time::{Duration, ext::NumericalDuration}; + /// assert_eq!(5.seconds().saturating_mul(2), 10.seconds()); + /// assert_eq!(5.seconds().saturating_mul(-2), (-10).seconds()); + /// assert_eq!(5.seconds().saturating_mul(0), Duration::ZERO); + /// assert_eq!(Duration::MAX.saturating_mul(2), Duration::MAX); + /// assert_eq!(Duration::MIN.saturating_mul(2), Duration::MIN); + /// assert_eq!(Duration::MAX.saturating_mul(-2), Duration::MIN); + /// assert_eq!(Duration::MIN.saturating_mul(-2), Duration::MAX); + /// ``` + pub const fn saturating_mul(self, rhs: i32) -> Self { + // Multiply nanoseconds as i64, because it cannot overflow that way. + let total_nanos = self.nanoseconds as i64 * rhs as i64; + let extra_secs = total_nanos / 1_000_000_000; + let nanoseconds = (total_nanos % 1_000_000_000) as _; + let (seconds, overflow1) = self.seconds.overflowing_mul(rhs as _); + if overflow1 { + if self.seconds > 0 && rhs > 0 || self.seconds < 0 && rhs < 0 { + return Self::MAX; + } + return Self::MIN; + } + let (seconds, overflow2) = seconds.overflowing_add(extra_secs); + if overflow2 { + if self.seconds > 0 && rhs > 0 { + return Self::MAX; + } + return Self::MIN; + } + + Self::new_unchecked(seconds, nanoseconds) + } + // endregion saturating arithmetic + + /// Runs a closure, returning the duration of time it took to run. The return value of the + /// closure is provided in the second part of the tuple. + #[cfg(feature = "std")] + pub fn time_fn<T>(f: impl FnOnce() -> T) -> (Self, T) { + let start = Instant::now(); + let return_value = f(); + let end = Instant::now(); + + (end - start, return_value) + } +} + +// region: trait impls +/// The format returned by this implementation is not stable and must not be relied upon. +/// +/// By default this produces an exact, full-precision printout of the duration. +/// For a concise, rounded printout instead, you can use the `.N` format specifier: +/// +/// ``` +/// # use time::Duration; +/// # +/// let duration = Duration::new(123456, 789011223); +/// println!("{duration:.3}"); +/// ``` +/// +/// For the purposes of this implementation, a day is exactly 24 hours and a minute is exactly 60 +/// seconds. +impl fmt::Display for Duration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_negative() { + f.write_str("-")?; + } + + if let Some(_precision) = f.precision() { + // Concise, rounded representation. + + if self.is_zero() { + // Write a zero value with the requested precision. + return (0.).fmt(f).and_then(|_| f.write_str("s")); + } + + /// Format the first item that produces a value greater than 1 and then break. + macro_rules! item { + ($name:literal, $value:expr) => { + let value = $value; + if value >= 1.0 { + return value.fmt(f).and_then(|_| f.write_str($name)); + } + }; + } + + // Even if this produces a de-normal float, because we're rounding we don't really care. + let seconds = self.unsigned_abs().as_secs_f64(); + + item!("d", seconds / 86_400.); + item!("h", seconds / 3_600.); + item!("m", seconds / 60.); + item!("s", seconds); + item!("ms", seconds * 1_000.); + item!("µs", seconds * 1_000_000.); + item!("ns", seconds * 1_000_000_000.); + } else { + // Precise, but verbose representation. + + if self.is_zero() { + return f.write_str("0s"); + } + + /// Format a single item. + macro_rules! item { + ($name:literal, $value:expr) => { + match $value { + 0 => Ok(()), + value => value.fmt(f).and_then(|_| f.write_str($name)), + } + }; + } + + let seconds = self.seconds.unsigned_abs(); + let nanoseconds = self.nanoseconds.unsigned_abs(); + + item!("d", seconds / 86_400)?; + item!("h", seconds / 3_600 % 24)?; + item!("m", seconds / 60 % 60)?; + item!("s", seconds % 60)?; + item!("ms", nanoseconds / 1_000_000)?; + item!("µs", nanoseconds / 1_000 % 1_000)?; + item!("ns", nanoseconds % 1_000)?; + } + + Ok(()) + } +} + +impl TryFrom<StdDuration> for Duration { + type Error = error::ConversionRange; + + fn try_from(original: StdDuration) -> Result<Self, error::ConversionRange> { + Ok(Self::new( + original + .as_secs() + .try_into() + .map_err(|_| error::ConversionRange)?, + original.subsec_nanos() as _, + )) + } +} + +impl TryFrom<Duration> for StdDuration { + type Error = error::ConversionRange; + + fn try_from(duration: Duration) -> Result<Self, error::ConversionRange> { + Ok(Self::new( + duration + .seconds + .try_into() + .map_err(|_| error::ConversionRange)?, + duration + .nanoseconds + .try_into() + .map_err(|_| error::ConversionRange)?, + )) + } +} + +impl Add for Duration { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + self.checked_add(rhs) + .expect("overflow when adding durations") + } +} + +impl Add<StdDuration> for Duration { + type Output = Self; + + fn add(self, std_duration: StdDuration) -> Self::Output { + self + Self::try_from(std_duration) + .expect("overflow converting `std::time::Duration` to `time::Duration`") + } +} + +impl Add<Duration> for StdDuration { + type Output = Duration; + + fn add(self, rhs: Duration) -> Self::Output { + rhs + self + } +} + +impl_add_assign!(Duration: Self, StdDuration); + +impl AddAssign<Duration> for StdDuration { + fn add_assign(&mut self, rhs: Duration) { + *self = (*self + rhs).try_into().expect( + "Cannot represent a resulting duration in std. Try `let x = x + rhs;`, which will \ + change the type.", + ); + } +} + +impl Neg for Duration { + type Output = Self; + + fn neg(self) -> Self::Output { + Self::new_unchecked(-self.seconds, -self.nanoseconds) + } +} + +impl Sub for Duration { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + self.checked_sub(rhs) + .expect("overflow when subtracting durations") + } +} + +impl Sub<StdDuration> for Duration { + type Output = Self; + + fn sub(self, rhs: StdDuration) -> Self::Output { + self - Self::try_from(rhs) + .expect("overflow converting `std::time::Duration` to `time::Duration`") + } +} + +impl Sub<Duration> for StdDuration { + type Output = Duration; + + fn sub(self, rhs: Duration) -> Self::Output { + Duration::try_from(self) + .expect("overflow converting `std::time::Duration` to `time::Duration`") + - rhs + } +} + +impl_sub_assign!(Duration: Self, StdDuration); + +impl SubAssign<Duration> for StdDuration { + fn sub_assign(&mut self, rhs: Duration) { + *self = (*self - rhs).try_into().expect( + "Cannot represent a resulting duration in std. Try `let x = x - rhs;`, which will \ + change the type.", + ); + } +} + +/// Implement `Mul` (reflexively) and `Div` for `Duration` for various types. +macro_rules! duration_mul_div_int { + ($($type:ty),+) => {$( + impl Mul<$type> for Duration { + type Output = Self; + + fn mul(self, rhs: $type) -> Self::Output { + Self::nanoseconds_i128( + self.whole_nanoseconds() + .checked_mul(rhs as _) + .expect("overflow when multiplying duration") + ) + } + } + + impl Mul<Duration> for $type { + type Output = Duration; + + fn mul(self, rhs: Duration) -> Self::Output { + rhs * self + } + } + + impl Div<$type> for Duration { + type Output = Self; + + fn div(self, rhs: $type) -> Self::Output { + Self::nanoseconds_i128(self.whole_nanoseconds() / rhs as i128) + } + } + )+}; +} +duration_mul_div_int![i8, i16, i32, u8, u16, u32]; + +impl Mul<f32> for Duration { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self::seconds_f32(self.as_seconds_f32() * rhs) + } +} + +impl Mul<Duration> for f32 { + type Output = Duration; + + fn mul(self, rhs: Duration) -> Self::Output { + rhs * self + } +} + +impl Mul<f64> for Duration { + type Output = Self; + + fn mul(self, rhs: f64) -> Self::Output { + Self::seconds_f64(self.as_seconds_f64() * rhs) + } +} + +impl Mul<Duration> for f64 { + type Output = Duration; + + fn mul(self, rhs: Duration) -> Self::Output { + rhs * self + } +} + +impl_mul_assign!(Duration: i8, i16, i32, u8, u16, u32, f32, f64); + +impl Div<f32> for Duration { + type Output = Self; + + fn div(self, rhs: f32) -> Self::Output { + Self::seconds_f32(self.as_seconds_f32() / rhs) + } +} + +impl Div<f64> for Duration { + type Output = Self; + + fn div(self, rhs: f64) -> Self::Output { + Self::seconds_f64(self.as_seconds_f64() / rhs) + } +} + +impl_div_assign!(Duration: i8, i16, i32, u8, u16, u32, f32, f64); + +impl Div for Duration { + type Output = f64; + + fn div(self, rhs: Self) -> Self::Output { + self.as_seconds_f64() / rhs.as_seconds_f64() + } +} + +impl Div<StdDuration> for Duration { + type Output = f64; + + fn div(self, rhs: StdDuration) -> Self::Output { + self.as_seconds_f64() / rhs.as_secs_f64() + } +} + +impl Div<Duration> for StdDuration { + type Output = f64; + + fn div(self, rhs: Duration) -> Self::Output { + self.as_secs_f64() / rhs.as_seconds_f64() + } +} + +impl PartialEq<StdDuration> for Duration { + fn eq(&self, rhs: &StdDuration) -> bool { + Ok(*self) == Self::try_from(*rhs) + } +} + +impl PartialEq<Duration> for StdDuration { + fn eq(&self, rhs: &Duration) -> bool { + rhs == self + } +} + +impl PartialOrd<StdDuration> for Duration { + fn partial_cmp(&self, rhs: &StdDuration) -> Option<Ordering> { + if rhs.as_secs() > i64::MAX as _ { + return Some(Ordering::Less); + } + + Some( + self.seconds + .cmp(&(rhs.as_secs() as _)) + .then_with(|| self.nanoseconds.cmp(&(rhs.subsec_nanos() as _))), + ) + } +} + +impl PartialOrd<Duration> for StdDuration { + fn partial_cmp(&self, rhs: &Duration) -> Option<Ordering> { + rhs.partial_cmp(self).map(Ordering::reverse) + } +} + +impl Sum for Duration { + fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { + iter.reduce(|a, b| a + b).unwrap_or_default() + } +} + +impl<'a> Sum<&'a Self> for Duration { + fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self { + iter.copied().sum() + } +} +// endregion trait impls diff --git a/third_party/rust/time/src/error/component_range.rs b/third_party/rust/time/src/error/component_range.rs new file mode 100644 index 0000000000..f399356f4d --- /dev/null +++ b/third_party/rust/time/src/error/component_range.rs @@ -0,0 +1,92 @@ +//! Component range error + +use core::fmt; + +use crate::error; + +/// An error type indicating that a component provided to a method was out of range, causing a +/// failure. +// i64 is the narrowest type fitting all use cases. This eliminates the need for a type parameter. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ComponentRange { + /// Name of the component. + pub(crate) name: &'static str, + /// Minimum allowed value, inclusive. + pub(crate) minimum: i64, + /// Maximum allowed value, inclusive. + pub(crate) maximum: i64, + /// Value that was provided. + pub(crate) value: i64, + /// The minimum and/or maximum value is conditional on the value of other + /// parameters. + pub(crate) conditional_range: bool, +} + +impl ComponentRange { + /// Obtain the name of the component whose value was out of range. + pub const fn name(self) -> &'static str { + self.name + } + + /// Whether the value's permitted range is conditional, i.e. whether an input with this + /// value could have succeeded if the values of other components were different. + pub const fn is_conditional(self) -> bool { + self.conditional_range + } +} + +impl fmt::Display for ComponentRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} must be in the range {}..={}", + self.name, self.minimum, self.maximum + )?; + + if self.conditional_range { + f.write_str(", given values of other parameters")?; + } + + Ok(()) + } +} + +impl From<ComponentRange> for crate::Error { + fn from(original: ComponentRange) -> Self { + Self::ComponentRange(original) + } +} + +impl TryFrom<crate::Error> for ComponentRange { + type Error = error::DifferentVariant; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::ComponentRange(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} + +/// **This trait implementation is deprecated and will be removed in a future breaking release.** +#[cfg(feature = "serde")] +impl serde::de::Expected for ComponentRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "a value in the range {}..={}", + self.minimum, self.maximum + ) + } +} + +#[cfg(feature = "serde")] +impl ComponentRange { + /// Convert the error to a deserialization error. + pub(crate) fn into_de_error<E: serde::de::Error>(self) -> E { + E::invalid_value(serde::de::Unexpected::Signed(self.value), &self) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ComponentRange {} diff --git a/third_party/rust/time/src/error/conversion_range.rs b/third_party/rust/time/src/error/conversion_range.rs new file mode 100644 index 0000000000..d6d9243e13 --- /dev/null +++ b/third_party/rust/time/src/error/conversion_range.rs @@ -0,0 +1,36 @@ +//! Conversion range error + +use core::fmt; + +use crate::error; + +/// An error type indicating that a conversion failed because the target type could not store the +/// initial value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ConversionRange; + +impl fmt::Display for ConversionRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Source value is out of range for the target type") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ConversionRange {} + +impl From<ConversionRange> for crate::Error { + fn from(err: ConversionRange) -> Self { + Self::ConversionRange(err) + } +} + +impl TryFrom<crate::Error> for ConversionRange { + type Error = error::DifferentVariant; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::ConversionRange(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} diff --git a/third_party/rust/time/src/error/different_variant.rs b/third_party/rust/time/src/error/different_variant.rs new file mode 100644 index 0000000000..22e21cb0c0 --- /dev/null +++ b/third_party/rust/time/src/error/different_variant.rs @@ -0,0 +1,34 @@ +//! Different variant error + +use core::fmt; + +/// An error type indicating that a [`TryFrom`](core::convert::TryFrom) call failed because the +/// original value was of a different variant. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DifferentVariant; + +impl fmt::Display for DifferentVariant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "value was of a different variant than required") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for DifferentVariant {} + +impl From<DifferentVariant> for crate::Error { + fn from(err: DifferentVariant) -> Self { + Self::DifferentVariant(err) + } +} + +impl TryFrom<crate::Error> for DifferentVariant { + type Error = Self; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::DifferentVariant(err) => Ok(err), + _ => Err(Self), + } + } +} diff --git a/third_party/rust/time/src/error/format.rs b/third_party/rust/time/src/error/format.rs new file mode 100644 index 0000000000..94d134363d --- /dev/null +++ b/third_party/rust/time/src/error/format.rs @@ -0,0 +1,92 @@ +//! Error formatting a struct + +use core::fmt; +use std::io; + +use crate::error; + +/// An error occurred when formatting. +#[non_exhaustive] +#[allow(missing_copy_implementations)] +#[derive(Debug)] +pub enum Format { + /// The type being formatted does not contain sufficient information to format a component. + #[non_exhaustive] + InsufficientTypeInformation, + /// The component named has a value that cannot be formatted into the requested format. + /// + /// This variant is only returned when using well-known formats. + InvalidComponent(&'static str), + /// A value of `std::io::Error` was returned internally. + StdIo(io::Error), +} + +impl fmt::Display for Format { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InsufficientTypeInformation => f.write_str( + "The type being formatted does not contain sufficient information to format a \ + component.", + ), + Self::InvalidComponent(component) => write!( + f, + "The {component} component cannot be formatted into the requested format." + ), + Self::StdIo(err) => err.fmt(f), + } + } +} + +impl From<io::Error> for Format { + fn from(err: io::Error) -> Self { + Self::StdIo(err) + } +} + +impl TryFrom<Format> for io::Error { + type Error = error::DifferentVariant; + + fn try_from(err: Format) -> Result<Self, Self::Error> { + match err { + Format::StdIo(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Format { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::InsufficientTypeInformation | Self::InvalidComponent(_) => None, + Self::StdIo(ref err) => Some(err), + } + } +} + +impl From<Format> for crate::Error { + fn from(original: Format) -> Self { + Self::Format(original) + } +} + +impl TryFrom<crate::Error> for Format { + type Error = error::DifferentVariant; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::Format(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} + +#[cfg(feature = "serde")] +impl Format { + /// Obtain an error type for the serializer. + #[doc(hidden)] // Exposed only for the `declare_format_string` macro + pub fn into_invalid_serde_value<S: serde::Serializer>(self) -> S::Error { + use serde::ser::Error; + S::Error::custom(self) + } +} diff --git a/third_party/rust/time/src/error/indeterminate_offset.rs b/third_party/rust/time/src/error/indeterminate_offset.rs new file mode 100644 index 0000000000..d25d4164ec --- /dev/null +++ b/third_party/rust/time/src/error/indeterminate_offset.rs @@ -0,0 +1,35 @@ +//! Indeterminate offset + +use core::fmt; + +use crate::error; + +/// The system's UTC offset could not be determined at the given datetime. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IndeterminateOffset; + +impl fmt::Display for IndeterminateOffset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("The system's UTC offset could not be determined") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for IndeterminateOffset {} + +impl From<IndeterminateOffset> for crate::Error { + fn from(err: IndeterminateOffset) -> Self { + Self::IndeterminateOffset(err) + } +} + +impl TryFrom<crate::Error> for IndeterminateOffset { + type Error = error::DifferentVariant; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::IndeterminateOffset(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} diff --git a/third_party/rust/time/src/error/invalid_format_description.rs b/third_party/rust/time/src/error/invalid_format_description.rs new file mode 100644 index 0000000000..29c46edb16 --- /dev/null +++ b/third_party/rust/time/src/error/invalid_format_description.rs @@ -0,0 +1,80 @@ +//! Invalid format description + +use alloc::string::String; +use core::fmt; + +use crate::error; + +/// The format description provided was not valid. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum InvalidFormatDescription { + /// There was a bracket pair that was opened but not closed. + #[non_exhaustive] + UnclosedOpeningBracket { + /// The zero-based index of the opening bracket. + index: usize, + }, + /// A component name is not valid. + #[non_exhaustive] + InvalidComponentName { + /// The name of the invalid component name. + name: String, + /// The zero-based index the component name starts at. + index: usize, + }, + /// A modifier is not valid. + #[non_exhaustive] + InvalidModifier { + /// The value of the invalid modifier. + value: String, + /// The zero-based index the modifier starts at. + index: usize, + }, + /// A component name is missing. + #[non_exhaustive] + MissingComponentName { + /// The zero-based index where the component name should start. + index: usize, + }, +} + +impl From<InvalidFormatDescription> for crate::Error { + fn from(original: InvalidFormatDescription) -> Self { + Self::InvalidFormatDescription(original) + } +} + +impl TryFrom<crate::Error> for InvalidFormatDescription { + type Error = error::DifferentVariant; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::InvalidFormatDescription(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} + +impl fmt::Display for InvalidFormatDescription { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use InvalidFormatDescription::*; + match self { + UnclosedOpeningBracket { index } => { + write!(f, "unclosed opening bracket at byte index {index}") + } + InvalidComponentName { name, index } => { + write!(f, "invalid component name `{name}` at byte index {index}") + } + InvalidModifier { value, index } => { + write!(f, "invalid modifier `{value}` at byte index {index}") + } + MissingComponentName { index } => { + write!(f, "missing component name at byte index {index}") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InvalidFormatDescription {} diff --git a/third_party/rust/time/src/error/invalid_variant.rs b/third_party/rust/time/src/error/invalid_variant.rs new file mode 100644 index 0000000000..396a749a29 --- /dev/null +++ b/third_party/rust/time/src/error/invalid_variant.rs @@ -0,0 +1,34 @@ +//! Invalid variant error + +use core::fmt; + +/// An error type indicating that a [`FromStr`](core::str::FromStr) call failed because the value +/// was not a valid variant. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InvalidVariant; + +impl fmt::Display for InvalidVariant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "value was not a valid variant") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InvalidVariant {} + +impl From<InvalidVariant> for crate::Error { + fn from(err: InvalidVariant) -> Self { + Self::InvalidVariant(err) + } +} + +impl TryFrom<crate::Error> for InvalidVariant { + type Error = crate::error::DifferentVariant; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::InvalidVariant(err) => Ok(err), + _ => Err(crate::error::DifferentVariant), + } + } +} diff --git a/third_party/rust/time/src/error/mod.rs b/third_party/rust/time/src/error/mod.rs new file mode 100644 index 0000000000..346b89f748 --- /dev/null +++ b/third_party/rust/time/src/error/mod.rs @@ -0,0 +1,112 @@ +//! Various error types returned by methods in the time crate. + +mod component_range; +mod conversion_range; +mod different_variant; +#[cfg(feature = "formatting")] +mod format; +#[cfg(feature = "local-offset")] +mod indeterminate_offset; +#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))] +mod invalid_format_description; +mod invalid_variant; +#[cfg(feature = "parsing")] +mod parse; +#[cfg(feature = "parsing")] +mod parse_from_description; +#[cfg(feature = "parsing")] +mod try_from_parsed; + +use core::fmt; + +pub use component_range::ComponentRange; +pub use conversion_range::ConversionRange; +pub use different_variant::DifferentVariant; +#[cfg(feature = "formatting")] +pub use format::Format; +#[cfg(feature = "local-offset")] +pub use indeterminate_offset::IndeterminateOffset; +#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))] +pub use invalid_format_description::InvalidFormatDescription; +pub use invalid_variant::InvalidVariant; +#[cfg(feature = "parsing")] +pub use parse::Parse; +#[cfg(feature = "parsing")] +pub use parse_from_description::ParseFromDescription; +#[cfg(feature = "parsing")] +pub use try_from_parsed::TryFromParsed; + +/// A unified error type for anything returned by a method in the time crate. +/// +/// This can be used when you either don't know or don't care about the exact error returned. +/// `Result<_, time::Error>` (or its alias `time::Result<_>`) will work in these situations. +#[allow(missing_copy_implementations, variant_size_differences)] +#[allow(clippy::missing_docs_in_private_items)] // variants only +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + ConversionRange(ConversionRange), + ComponentRange(ComponentRange), + #[cfg(feature = "local-offset")] + IndeterminateOffset(IndeterminateOffset), + #[cfg(feature = "formatting")] + Format(Format), + #[cfg(feature = "parsing")] + ParseFromDescription(ParseFromDescription), + #[cfg(feature = "parsing")] + #[non_exhaustive] + UnexpectedTrailingCharacters, + #[cfg(feature = "parsing")] + TryFromParsed(TryFromParsed), + #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))] + InvalidFormatDescription(InvalidFormatDescription), + DifferentVariant(DifferentVariant), + InvalidVariant(InvalidVariant), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ConversionRange(e) => e.fmt(f), + Self::ComponentRange(e) => e.fmt(f), + #[cfg(feature = "local-offset")] + Self::IndeterminateOffset(e) => e.fmt(f), + #[cfg(feature = "formatting")] + Self::Format(e) => e.fmt(f), + #[cfg(feature = "parsing")] + Self::ParseFromDescription(e) => e.fmt(f), + #[cfg(feature = "parsing")] + Self::UnexpectedTrailingCharacters => f.write_str("unexpected trailing characters"), + #[cfg(feature = "parsing")] + Self::TryFromParsed(e) => e.fmt(f), + #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))] + Self::InvalidFormatDescription(e) => e.fmt(f), + Self::DifferentVariant(e) => e.fmt(f), + Self::InvalidVariant(e) => e.fmt(f), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::ConversionRange(err) => Some(err), + Self::ComponentRange(err) => Some(err), + #[cfg(feature = "local-offset")] + Self::IndeterminateOffset(err) => Some(err), + #[cfg(feature = "formatting")] + Self::Format(err) => Some(err), + #[cfg(feature = "parsing")] + Self::ParseFromDescription(err) => Some(err), + #[cfg(feature = "parsing")] + Self::UnexpectedTrailingCharacters => None, + #[cfg(feature = "parsing")] + Self::TryFromParsed(err) => Some(err), + #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))] + Self::InvalidFormatDescription(err) => Some(err), + Self::DifferentVariant(err) => Some(err), + Self::InvalidVariant(err) => Some(err), + } + } +} diff --git a/third_party/rust/time/src/error/parse.rs b/third_party/rust/time/src/error/parse.rs new file mode 100644 index 0000000000..b90ac74e73 --- /dev/null +++ b/third_party/rust/time/src/error/parse.rs @@ -0,0 +1,97 @@ +//! Error that occurred at some stage of parsing + +use core::fmt; + +use crate::error::{self, ParseFromDescription, TryFromParsed}; + +/// An error that occurred at some stage of parsing. +#[allow(variant_size_differences)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Parse { + #[allow(clippy::missing_docs_in_private_items)] + TryFromParsed(TryFromParsed), + #[allow(clippy::missing_docs_in_private_items)] + ParseFromDescription(ParseFromDescription), + /// The input should have ended, but there were characters remaining. + #[non_exhaustive] + UnexpectedTrailingCharacters, +} + +impl fmt::Display for Parse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::TryFromParsed(err) => err.fmt(f), + Self::ParseFromDescription(err) => err.fmt(f), + Self::UnexpectedTrailingCharacters => f.write_str("unexpected trailing characters"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Parse { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::TryFromParsed(err) => Some(err), + Self::ParseFromDescription(err) => Some(err), + Self::UnexpectedTrailingCharacters => None, + } + } +} + +impl From<TryFromParsed> for Parse { + fn from(err: TryFromParsed) -> Self { + Self::TryFromParsed(err) + } +} + +impl TryFrom<Parse> for TryFromParsed { + type Error = error::DifferentVariant; + + fn try_from(err: Parse) -> Result<Self, Self::Error> { + match err { + Parse::TryFromParsed(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} + +impl From<ParseFromDescription> for Parse { + fn from(err: ParseFromDescription) -> Self { + Self::ParseFromDescription(err) + } +} + +impl TryFrom<Parse> for ParseFromDescription { + type Error = error::DifferentVariant; + + fn try_from(err: Parse) -> Result<Self, Self::Error> { + match err { + Parse::ParseFromDescription(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} + +impl From<Parse> for crate::Error { + fn from(err: Parse) -> Self { + match err { + Parse::TryFromParsed(err) => Self::TryFromParsed(err), + Parse::ParseFromDescription(err) => Self::ParseFromDescription(err), + Parse::UnexpectedTrailingCharacters => Self::UnexpectedTrailingCharacters, + } + } +} + +impl TryFrom<crate::Error> for Parse { + type Error = error::DifferentVariant; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::ParseFromDescription(err) => Ok(Self::ParseFromDescription(err)), + crate::Error::UnexpectedTrailingCharacters => Ok(Self::UnexpectedTrailingCharacters), + crate::Error::TryFromParsed(err) => Ok(Self::TryFromParsed(err)), + _ => Err(error::DifferentVariant), + } + } +} diff --git a/third_party/rust/time/src/error/parse_from_description.rs b/third_party/rust/time/src/error/parse_from_description.rs new file mode 100644 index 0000000000..772f10d173 --- /dev/null +++ b/third_party/rust/time/src/error/parse_from_description.rs @@ -0,0 +1,47 @@ +//! Error parsing an input into a [`Parsed`](crate::parsing::Parsed) struct + +use core::fmt; + +use crate::error; + +/// An error that occurred while parsing the input into a [`Parsed`](crate::parsing::Parsed) struct. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ParseFromDescription { + /// A string literal was not what was expected. + #[non_exhaustive] + InvalidLiteral, + /// A dynamic component was not valid. + InvalidComponent(&'static str), +} + +impl fmt::Display for ParseFromDescription { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidLiteral => f.write_str("a character literal was not valid"), + Self::InvalidComponent(name) => { + write!(f, "the '{name}' component could not be parsed") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseFromDescription {} + +impl From<ParseFromDescription> for crate::Error { + fn from(original: ParseFromDescription) -> Self { + Self::ParseFromDescription(original) + } +} + +impl TryFrom<crate::Error> for ParseFromDescription { + type Error = error::DifferentVariant; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::ParseFromDescription(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} diff --git a/third_party/rust/time/src/error/try_from_parsed.rs b/third_party/rust/time/src/error/try_from_parsed.rs new file mode 100644 index 0000000000..da8e6947a3 --- /dev/null +++ b/third_party/rust/time/src/error/try_from_parsed.rs @@ -0,0 +1,71 @@ +//! Error converting a [`Parsed`](crate::parsing::Parsed) struct to another type + +use core::fmt; + +use crate::error; + +/// An error that occurred when converting a [`Parsed`](crate::parsing::Parsed) to another type. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TryFromParsed { + /// The [`Parsed`](crate::parsing::Parsed) did not include enough information to construct the + /// type. + InsufficientInformation, + /// Some component contained an invalid value for the type. + ComponentRange(error::ComponentRange), +} + +impl fmt::Display for TryFromParsed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InsufficientInformation => f.write_str( + "the `Parsed` struct did not include enough information to construct the type", + ), + Self::ComponentRange(err) => err.fmt(f), + } + } +} + +impl From<error::ComponentRange> for TryFromParsed { + fn from(v: error::ComponentRange) -> Self { + Self::ComponentRange(v) + } +} + +impl TryFrom<TryFromParsed> for error::ComponentRange { + type Error = error::DifferentVariant; + + fn try_from(err: TryFromParsed) -> Result<Self, Self::Error> { + match err { + TryFromParsed::ComponentRange(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TryFromParsed { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InsufficientInformation => None, + Self::ComponentRange(err) => Some(err), + } + } +} + +impl From<TryFromParsed> for crate::Error { + fn from(original: TryFromParsed) -> Self { + Self::TryFromParsed(original) + } +} + +impl TryFrom<crate::Error> for TryFromParsed { + type Error = error::DifferentVariant; + + fn try_from(err: crate::Error) -> Result<Self, Self::Error> { + match err { + crate::Error::TryFromParsed(err) => Ok(err), + _ => Err(error::DifferentVariant), + } + } +} diff --git a/third_party/rust/time/src/ext.rs b/third_party/rust/time/src/ext.rs new file mode 100644 index 0000000000..5a1393d86f --- /dev/null +++ b/third_party/rust/time/src/ext.rs @@ -0,0 +1,279 @@ +//! Extension traits. + +use core::time::Duration as StdDuration; + +use crate::Duration; + +/// Sealed trait to prevent downstream implementations. +mod sealed { + /// A trait that cannot be implemented by downstream users. + pub trait Sealed {} + impl Sealed for i64 {} + impl Sealed for u64 {} + impl Sealed for f64 {} +} + +// region: NumericalDuration +/// Create [`Duration`]s from numeric literals. +/// +/// # Examples +/// +/// Basic construction of [`Duration`]s. +/// +/// ```rust +/// # use time::{Duration, ext::NumericalDuration}; +/// assert_eq!(5.nanoseconds(), Duration::nanoseconds(5)); +/// assert_eq!(5.microseconds(), Duration::microseconds(5)); +/// assert_eq!(5.milliseconds(), Duration::milliseconds(5)); +/// assert_eq!(5.seconds(), Duration::seconds(5)); +/// assert_eq!(5.minutes(), Duration::minutes(5)); +/// assert_eq!(5.hours(), Duration::hours(5)); +/// assert_eq!(5.days(), Duration::days(5)); +/// assert_eq!(5.weeks(), Duration::weeks(5)); +/// ``` +/// +/// Signed integers work as well! +/// +/// ```rust +/// # use time::{Duration, ext::NumericalDuration}; +/// assert_eq!((-5).nanoseconds(), Duration::nanoseconds(-5)); +/// assert_eq!((-5).microseconds(), Duration::microseconds(-5)); +/// assert_eq!((-5).milliseconds(), Duration::milliseconds(-5)); +/// assert_eq!((-5).seconds(), Duration::seconds(-5)); +/// assert_eq!((-5).minutes(), Duration::minutes(-5)); +/// assert_eq!((-5).hours(), Duration::hours(-5)); +/// assert_eq!((-5).days(), Duration::days(-5)); +/// assert_eq!((-5).weeks(), Duration::weeks(-5)); +/// ``` +/// +/// Just like any other [`Duration`], they can be added, subtracted, etc. +/// +/// ```rust +/// # use time::ext::NumericalDuration; +/// assert_eq!(2.seconds() + 500.milliseconds(), 2_500.milliseconds()); +/// assert_eq!(2.seconds() - 500.milliseconds(), 1_500.milliseconds()); +/// ``` +/// +/// When called on floating point values, any remainder of the floating point value will be +/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited +/// capacity. +pub trait NumericalDuration: sealed::Sealed { + /// Create a [`Duration`] from the number of nanoseconds. + fn nanoseconds(self) -> Duration; + /// Create a [`Duration`] from the number of microseconds. + fn microseconds(self) -> Duration; + /// Create a [`Duration`] from the number of milliseconds. + fn milliseconds(self) -> Duration; + /// Create a [`Duration`] from the number of seconds. + fn seconds(self) -> Duration; + /// Create a [`Duration`] from the number of minutes. + fn minutes(self) -> Duration; + /// Create a [`Duration`] from the number of hours. + fn hours(self) -> Duration; + /// Create a [`Duration`] from the number of days. + fn days(self) -> Duration; + /// Create a [`Duration`] from the number of weeks. + fn weeks(self) -> Duration; +} + +impl NumericalDuration for i64 { + fn nanoseconds(self) -> Duration { + Duration::nanoseconds(self) + } + + fn microseconds(self) -> Duration { + Duration::microseconds(self) + } + + fn milliseconds(self) -> Duration { + Duration::milliseconds(self) + } + + fn seconds(self) -> Duration { + Duration::seconds(self) + } + + fn minutes(self) -> Duration { + Duration::minutes(self) + } + + fn hours(self) -> Duration { + Duration::hours(self) + } + + fn days(self) -> Duration { + Duration::days(self) + } + + fn weeks(self) -> Duration { + Duration::weeks(self) + } +} + +impl NumericalDuration for f64 { + fn nanoseconds(self) -> Duration { + Duration::nanoseconds(self as _) + } + + fn microseconds(self) -> Duration { + Duration::nanoseconds((self * 1_000.) as _) + } + + fn milliseconds(self) -> Duration { + Duration::nanoseconds((self * 1_000_000.) as _) + } + + fn seconds(self) -> Duration { + Duration::nanoseconds((self * 1_000_000_000.) as _) + } + + fn minutes(self) -> Duration { + Duration::nanoseconds((self * 60_000_000_000.) as _) + } + + fn hours(self) -> Duration { + Duration::nanoseconds((self * 3_600_000_000_000.) as _) + } + + fn days(self) -> Duration { + Duration::nanoseconds((self * 86_400_000_000_000.) as _) + } + + fn weeks(self) -> Duration { + Duration::nanoseconds((self * 604_800_000_000_000.) as _) + } +} +// endregion NumericalDuration + +// region: NumericalStdDuration +/// Create [`std::time::Duration`]s from numeric literals. +/// +/// # Examples +/// +/// Basic construction of [`std::time::Duration`]s. +/// +/// ```rust +/// # use time::ext::NumericalStdDuration; +/// # use core::time::Duration; +/// assert_eq!(5.std_nanoseconds(), Duration::from_nanos(5)); +/// assert_eq!(5.std_microseconds(), Duration::from_micros(5)); +/// assert_eq!(5.std_milliseconds(), Duration::from_millis(5)); +/// assert_eq!(5.std_seconds(), Duration::from_secs(5)); +/// assert_eq!(5.std_minutes(), Duration::from_secs(5 * 60)); +/// assert_eq!(5.std_hours(), Duration::from_secs(5 * 3_600)); +/// assert_eq!(5.std_days(), Duration::from_secs(5 * 86_400)); +/// assert_eq!(5.std_weeks(), Duration::from_secs(5 * 604_800)); +/// ``` +/// +/// Just like any other [`std::time::Duration`], they can be added, subtracted, etc. +/// +/// ```rust +/// # use time::ext::NumericalStdDuration; +/// assert_eq!( +/// 2.std_seconds() + 500.std_milliseconds(), +/// 2_500.std_milliseconds() +/// ); +/// assert_eq!( +/// 2.std_seconds() - 500.std_milliseconds(), +/// 1_500.std_milliseconds() +/// ); +/// ``` +/// +/// When called on floating point values, any remainder of the floating point value will be +/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited +/// capacity. +pub trait NumericalStdDuration: sealed::Sealed { + /// Create a [`std::time::Duration`] from the number of nanoseconds. + fn std_nanoseconds(self) -> StdDuration; + /// Create a [`std::time::Duration`] from the number of microseconds. + fn std_microseconds(self) -> StdDuration; + /// Create a [`std::time::Duration`] from the number of milliseconds. + fn std_milliseconds(self) -> StdDuration; + /// Create a [`std::time::Duration`] from the number of seconds. + fn std_seconds(self) -> StdDuration; + /// Create a [`std::time::Duration`] from the number of minutes. + fn std_minutes(self) -> StdDuration; + /// Create a [`std::time::Duration`] from the number of hours. + fn std_hours(self) -> StdDuration; + /// Create a [`std::time::Duration`] from the number of days. + fn std_days(self) -> StdDuration; + /// Create a [`std::time::Duration`] from the number of weeks. + fn std_weeks(self) -> StdDuration; +} + +impl NumericalStdDuration for u64 { + fn std_nanoseconds(self) -> StdDuration { + StdDuration::from_nanos(self) + } + + fn std_microseconds(self) -> StdDuration { + StdDuration::from_micros(self) + } + + fn std_milliseconds(self) -> StdDuration { + StdDuration::from_millis(self) + } + + fn std_seconds(self) -> StdDuration { + StdDuration::from_secs(self) + } + + fn std_minutes(self) -> StdDuration { + StdDuration::from_secs(self * 60) + } + + fn std_hours(self) -> StdDuration { + StdDuration::from_secs(self * 3_600) + } + + fn std_days(self) -> StdDuration { + StdDuration::from_secs(self * 86_400) + } + + fn std_weeks(self) -> StdDuration { + StdDuration::from_secs(self * 604_800) + } +} + +impl NumericalStdDuration for f64 { + fn std_nanoseconds(self) -> StdDuration { + assert!(self >= 0.); + StdDuration::from_nanos(self as _) + } + + fn std_microseconds(self) -> StdDuration { + assert!(self >= 0.); + StdDuration::from_nanos((self * 1_000.) as _) + } + + fn std_milliseconds(self) -> StdDuration { + assert!(self >= 0.); + StdDuration::from_nanos((self * 1_000_000.) as _) + } + + fn std_seconds(self) -> StdDuration { + assert!(self >= 0.); + StdDuration::from_nanos((self * 1_000_000_000.) as _) + } + + fn std_minutes(self) -> StdDuration { + assert!(self >= 0.); + StdDuration::from_nanos((self * 60_000_000_000.) as _) + } + + fn std_hours(self) -> StdDuration { + assert!(self >= 0.); + StdDuration::from_nanos((self * 3_600_000_000_000.) as _) + } + + fn std_days(self) -> StdDuration { + assert!(self >= 0.); + StdDuration::from_nanos((self * 86_400_000_000_000.) as _) + } + + fn std_weeks(self) -> StdDuration { + assert!(self >= 0.); + StdDuration::from_nanos((self * 604_800_000_000_000.) as _) + } +} +// endregion NumericalStdDuration diff --git a/third_party/rust/time/src/format_description/borrowed_format_item.rs b/third_party/rust/time/src/format_description/borrowed_format_item.rs new file mode 100644 index 0000000000..425b902878 --- /dev/null +++ b/third_party/rust/time/src/format_description/borrowed_format_item.rs @@ -0,0 +1,106 @@ +//! A format item with borrowed data. + +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use core::fmt; + +use crate::error; +use crate::format_description::Component; + +/// A complete description of how to format and parse a type. +#[non_exhaustive] +#[cfg_attr(not(feature = "alloc"), derive(Debug))] +#[derive(Clone, PartialEq, Eq)] +pub enum BorrowedFormatItem<'a> { + /// Bytes that are formatted as-is. + /// + /// **Note**: If you call the `format` method that returns a `String`, these bytes will be + /// passed through `String::from_utf8_lossy`. + Literal(&'a [u8]), + /// A minimal representation of a single non-literal item. + Component(Component), + /// A series of literals or components that collectively form a partial or complete + /// description. + Compound(&'a [Self]), + /// A `FormatItem` that may or may not be present when parsing. If parsing fails, there + /// will be no effect on the resulting `struct`. + /// + /// This variant has no effect on formatting, as the value is guaranteed to be present. + Optional(&'a Self), + /// A series of `FormatItem`s where, when parsing, the first successful parse is used. When + /// formatting, the first element of the slice is used. An empty slice is a no-op when + /// formatting or parsing. + First(&'a [Self]), +} + +#[cfg(feature = "alloc")] +impl fmt::Debug for BorrowedFormatItem<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)), + Self::Component(component) => component.fmt(f), + Self::Compound(compound) => compound.fmt(f), + Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(), + Self::First(items) => f.debug_tuple("First").field(items).finish(), + } + } +} + +impl From<Component> for BorrowedFormatItem<'_> { + fn from(component: Component) -> Self { + Self::Component(component) + } +} + +impl TryFrom<BorrowedFormatItem<'_>> for Component { + type Error = error::DifferentVariant; + + fn try_from(value: BorrowedFormatItem<'_>) -> Result<Self, Self::Error> { + match value { + BorrowedFormatItem::Component(component) => Ok(component), + _ => Err(error::DifferentVariant), + } + } +} + +impl<'a> From<&'a [BorrowedFormatItem<'_>]> for BorrowedFormatItem<'a> { + fn from(items: &'a [BorrowedFormatItem<'_>]) -> Self { + Self::Compound(items) + } +} + +impl<'a> TryFrom<BorrowedFormatItem<'a>> for &[BorrowedFormatItem<'a>] { + type Error = error::DifferentVariant; + + fn try_from(value: BorrowedFormatItem<'a>) -> Result<Self, Self::Error> { + match value { + BorrowedFormatItem::Compound(items) => Ok(items), + _ => Err(error::DifferentVariant), + } + } +} + +impl PartialEq<Component> for BorrowedFormatItem<'_> { + fn eq(&self, rhs: &Component) -> bool { + matches!(self, Self::Component(component) if component == rhs) + } +} + +impl PartialEq<BorrowedFormatItem<'_>> for Component { + fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool { + rhs == self + } +} + +impl PartialEq<&[Self]> for BorrowedFormatItem<'_> { + fn eq(&self, rhs: &&[Self]) -> bool { + matches!(self, Self::Compound(compound) if compound == rhs) + } +} + +impl PartialEq<BorrowedFormatItem<'_>> for &[BorrowedFormatItem<'_>] { + fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool { + rhs == self + } +} diff --git a/third_party/rust/time/src/format_description/component.rs b/third_party/rust/time/src/format_description/component.rs new file mode 100644 index 0000000000..68d162e260 --- /dev/null +++ b/third_party/rust/time/src/format_description/component.rs @@ -0,0 +1,37 @@ +//! Part of a format description. + +use crate::format_description::modifier; + +/// A component of a larger format description. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Component { + /// Day of the month. + Day(modifier::Day), + /// Month of the year. + Month(modifier::Month), + /// Ordinal day of the year. + Ordinal(modifier::Ordinal), + /// Day of the week. + Weekday(modifier::Weekday), + /// Week within the year. + WeekNumber(modifier::WeekNumber), + /// Year of the date. + Year(modifier::Year), + /// Hour of the day. + Hour(modifier::Hour), + /// Minute within the hour. + Minute(modifier::Minute), + /// AM/PM part of the time. + Period(modifier::Period), + /// Second within the minute. + Second(modifier::Second), + /// Subsecond within the second. + Subsecond(modifier::Subsecond), + /// Hour of the UTC offset. + OffsetHour(modifier::OffsetHour), + /// Minute within the hour of the UTC offset. + OffsetMinute(modifier::OffsetMinute), + /// Second within the minute of the UTC offset. + OffsetSecond(modifier::OffsetSecond), +} diff --git a/third_party/rust/time/src/format_description/mod.rs b/third_party/rust/time/src/format_description/mod.rs new file mode 100644 index 0000000000..7712288e74 --- /dev/null +++ b/third_party/rust/time/src/format_description/mod.rs @@ -0,0 +1,34 @@ +//! Description of how types should be formatted and parsed. +//! +//! The formatted value will be output to the provided writer. Format descriptions can be +//! [well-known](crate::format_description::well_known) or obtained by using the +//! [`format_description!`](crate::macros::format_description) macro, the +//! [`format_description::parse`](crate::format_description::parse()) function. + +mod borrowed_format_item; +mod component; +pub mod modifier; +#[cfg(feature = "alloc")] +mod owned_format_item; +#[cfg(feature = "alloc")] +mod parse; + +pub use borrowed_format_item::BorrowedFormatItem as FormatItem; +#[cfg(feature = "alloc")] +pub use owned_format_item::OwnedFormatItem; + +pub use self::component::Component; +#[cfg(feature = "alloc")] +pub use self::parse::{parse, parse_owned}; + +/// Well-known formats, typically standards. +pub mod well_known { + pub mod iso8601; + mod rfc2822; + mod rfc3339; + + #[doc(inline)] + pub use iso8601::Iso8601; + pub use rfc2822::Rfc2822; + pub use rfc3339::Rfc3339; +} diff --git a/third_party/rust/time/src/format_description/modifier.rs b/third_party/rust/time/src/format_description/modifier.rs new file mode 100644 index 0000000000..e01c18fda4 --- /dev/null +++ b/third_party/rust/time/src/format_description/modifier.rs @@ -0,0 +1,355 @@ +//! Various modifiers for components. + +// region: date modifiers +/// Day of the month. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Day { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// The representation of a month. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MonthRepr { + /// The number of the month (January is 1, December is 12). + Numerical, + /// The long form of the month name (e.g. "January"). + Long, + /// The short form of the month name (e.g. "Jan"). + Short, +} + +/// Month of the year. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Month { + /// The padding to obtain the minimum width. + pub padding: Padding, + /// What form of representation should be used? + pub repr: MonthRepr, + /// Is the value case sensitive when parsing? + pub case_sensitive: bool, +} + +/// Ordinal day of the year. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Ordinal { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// The representation used for the day of the week. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WeekdayRepr { + /// The short form of the weekday (e.g. "Mon"). + Short, + /// The long form of the weekday (e.g. "Monday"). + Long, + /// A numerical representation using Sunday as the first day of the week. + /// + /// Sunday is either 0 or 1, depending on the other modifier's value. + Sunday, + /// A numerical representation using Monday as the first day of the week. + /// + /// Monday is either 0 or 1, depending on the other modifier's value. + Monday, +} + +/// Day of the week. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Weekday { + /// What form of representation should be used? + pub repr: WeekdayRepr, + /// When using a numerical representation, should it be zero or one-indexed? + pub one_indexed: bool, + /// Is the value case sensitive when parsing? + pub case_sensitive: bool, +} + +/// The representation used for the week number. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WeekNumberRepr { + /// Week 1 is the week that contains January 4. + Iso, + /// Week 1 begins on the first Sunday of the calendar year. + Sunday, + /// Week 1 begins on the first Monday of the calendar year. + Monday, +} + +/// Week within the year. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct WeekNumber { + /// The padding to obtain the minimum width. + pub padding: Padding, + /// What kind of representation should be used? + pub repr: WeekNumberRepr, +} + +/// The representation used for a year value. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum YearRepr { + /// The full value of the year. + Full, + /// Only the last two digits of the year. + LastTwo, +} + +/// Year of the date. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Year { + /// The padding to obtain the minimum width. + pub padding: Padding, + /// What kind of representation should be used? + pub repr: YearRepr, + /// Whether the value is based on the ISO week number or the Gregorian calendar. + pub iso_week_based: bool, + /// Whether the `+` sign is present when a positive year contains fewer than five digits. + pub sign_is_mandatory: bool, +} +// endregion date modifiers + +// region: time modifiers +/// Hour of the day. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Hour { + /// The padding to obtain the minimum width. + pub padding: Padding, + /// Is the hour displayed using a 12 or 24-hour clock? + pub is_12_hour_clock: bool, +} + +/// Minute within the hour. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Minute { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// AM/PM part of the time. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Period { + /// Is the period uppercase or lowercase? + pub is_uppercase: bool, + /// Is the value case sensitive when parsing? + /// + /// Note that when `false`, the `is_uppercase` field has no effect on parsing behavior. + pub case_sensitive: bool, +} + +/// Second within the minute. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Second { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// The number of digits present in a subsecond representation. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SubsecondDigits { + /// Exactly one digit. + One, + /// Exactly two digits. + Two, + /// Exactly three digits. + Three, + /// Exactly four digits. + Four, + /// Exactly five digits. + Five, + /// Exactly six digits. + Six, + /// Exactly seven digits. + Seven, + /// Exactly eight digits. + Eight, + /// Exactly nine digits. + Nine, + /// Any number of digits (up to nine) that is at least one. When formatting, the minimum digits + /// necessary will be used. + OneOrMore, +} + +/// Subsecond within the second. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Subsecond { + /// How many digits are present in the component? + pub digits: SubsecondDigits, +} +// endregion time modifiers + +// region: offset modifiers +/// Hour of the UTC offset. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OffsetHour { + /// Whether the `+` sign is present on positive values. + pub sign_is_mandatory: bool, + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// Minute within the hour of the UTC offset. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OffsetMinute { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// Second within the minute of the UTC offset. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OffsetSecond { + /// The padding to obtain the minimum width. + pub padding: Padding, +} +// endregion offset modifiers + +/// Type of padding to ensure a minimum width. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Padding { + /// A space character (` `) should be used as padding. + Space, + /// A zero character (`0`) should be used as padding. + Zero, + /// There is no padding. This can result in a width below the otherwise minimum number of + /// characters. + None, +} + +/// Generate the provided code if and only if `pub` is present. +macro_rules! if_pub { + (pub $(#[$attr:meta])*; $($x:tt)*) => { + $(#[$attr])* + /// + /// This function exists since [`Default::default()`] cannot be used in a `const` context. + /// It may be removed once that becomes possible. As the [`Default`] trait is in the + /// prelude, removing this function in the future will not cause any resolution failures for + /// the overwhelming majority of users; only users who use `#![no_implicit_prelude]` will be + /// affected. As such it will not be considered a breaking change. + $($x)* + }; + ($($_:tt)*) => {}; +} + +/// Implement `Default` for the given type. This also generates an inherent implementation of a +/// `default` method that is `const fn`, permitting the default value to be used in const contexts. +// Every modifier should use this macro rather than a derived `Default`. +macro_rules! impl_const_default { + ($($(#[$doc:meta])* $(@$pub:ident)? $type:ty => $default:expr;)*) => {$( + impl $type { + if_pub! { + $($pub)? + $(#[$doc])*; + pub const fn default() -> Self { + $default + } + } + } + + $(#[$doc])* + impl Default for $type { + fn default() -> Self { + $default + } + } + )*}; +} + +impl_const_default! { + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub Day => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value uses the + /// [`Numerical`](Self::Numerical) representation. + MonthRepr => Self::Numerical; + /// Creates an instance of this type that indicates the value uses the + /// [`Numerical`](MonthRepr::Numerical) representation, is [padded with zeroes](Padding::Zero), + /// and is case-sensitive when parsing. + @pub Month => Self { + padding: Padding::Zero, + repr: MonthRepr::Numerical, + case_sensitive: true, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub Ordinal => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value uses the [`Long`](Self::Long) representation. + WeekdayRepr => Self::Long; + /// Creates a modifier that indicates the value uses the [`Long`](WeekdayRepr::Long) + /// representation and is case-sensitive when parsing. If the representation is changed to a + /// numerical one, the instance defaults to one-based indexing. + @pub Weekday => Self { + repr: WeekdayRepr::Long, + one_indexed: true, + case_sensitive: true, + }; + /// Creates a modifier that indicates that the value uses the [`Iso`](Self::Iso) representation. + WeekNumberRepr => Self::Iso; + /// Creates a modifier that indicates that the value is [padded with zeroes](Padding::Zero) + /// and uses the [`Iso`](WeekNumberRepr::Iso) representation. + @pub WeekNumber => Self { + padding: Padding::Zero, + repr: WeekNumberRepr::Iso, + }; + /// Creates a modifier that indicates the value uses the [`Full`](Self::Full) representation. + YearRepr => Self::Full; + /// Creates a modifier that indicates the value uses the [`Full`](YearRepr::Full) + /// representation, is [padded with zeroes](Padding::Zero), uses the Gregorian calendar as its + /// base, and only includes the year's sign if necessary. + @pub Year => Self { + padding: Padding::Zero, + repr: YearRepr::Full, + iso_week_based: false, + sign_is_mandatory: false, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero) and + /// has the 24-hour representation. + @pub Hour => Self { + padding: Padding::Zero, + is_12_hour_clock: false, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub Minute => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value uses the upper-case representation and is + /// case-sensitive when parsing. + @pub Period => Self { + is_uppercase: true, + case_sensitive: true, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub Second => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the stringified value contains [one or more + /// digits](Self::OneOrMore). + SubsecondDigits => Self::OneOrMore; + /// Creates a modifier that indicates the stringified value contains [one or more + /// digits](SubsecondDigits::OneOrMore). + @pub Subsecond => Self { digits: SubsecondDigits::OneOrMore }; + /// Creates a modifier that indicates the value uses the `+` sign for all positive values + /// and is [padded with zeroes](Padding::Zero). + @pub OffsetHour => Self { + sign_is_mandatory: true, + padding: Padding::Zero, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub OffsetMinute => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub OffsetSecond => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value is [padded with zeroes](Self::Zero). + Padding => Self::Zero; +} diff --git a/third_party/rust/time/src/format_description/owned_format_item.rs b/third_party/rust/time/src/format_description/owned_format_item.rs new file mode 100644 index 0000000000..6f7f7c2337 --- /dev/null +++ b/third_party/rust/time/src/format_description/owned_format_item.rs @@ -0,0 +1,162 @@ +//! A format item with owned data. + +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec::Vec; +use core::fmt; + +use crate::error; +use crate::format_description::{Component, FormatItem}; + +/// A complete description of how to format and parse a type. +#[non_exhaustive] +#[derive(Clone, PartialEq, Eq)] +pub enum OwnedFormatItem { + /// Bytes that are formatted as-is. + /// + /// **Note**: If you call the `format` method that returns a `String`, these bytes will be + /// passed through `String::from_utf8_lossy`. + Literal(Box<[u8]>), + /// A minimal representation of a single non-literal item. + Component(Component), + /// A series of literals or components that collectively form a partial or complete + /// description. + Compound(Box<[Self]>), + /// A `FormatItem` that may or may not be present when parsing. If parsing fails, there + /// will be no effect on the resulting `struct`. + /// + /// This variant has no effect on formatting, as the value is guaranteed to be present. + Optional(Box<Self>), + /// A series of `FormatItem`s where, when parsing, the first successful parse is used. When + /// formatting, the first element of the [`Vec`] is used. An empty [`Vec`] is a no-op when + /// formatting or parsing. + First(Box<[Self]>), +} + +impl fmt::Debug for OwnedFormatItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)), + Self::Component(component) => component.fmt(f), + Self::Compound(compound) => compound.fmt(f), + Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(), + Self::First(items) => f.debug_tuple("First").field(items).finish(), + } + } +} + +// region: conversions from FormatItem +impl From<FormatItem<'_>> for OwnedFormatItem { + fn from(item: FormatItem<'_>) -> Self { + (&item).into() + } +} + +impl From<&FormatItem<'_>> for OwnedFormatItem { + fn from(item: &FormatItem<'_>) -> Self { + match item { + FormatItem::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()), + FormatItem::Component(component) => Self::Component(*component), + FormatItem::Compound(compound) => Self::Compound( + compound + .iter() + .cloned() + .map(Into::into) + .collect::<Vec<_>>() + .into_boxed_slice(), + ), + FormatItem::Optional(item) => Self::Optional(Box::new((*item).into())), + FormatItem::First(items) => Self::First( + items + .iter() + .cloned() + .map(Into::into) + .collect::<Vec<_>>() + .into_boxed_slice(), + ), + } + } +} + +impl From<Vec<FormatItem<'_>>> for OwnedFormatItem { + fn from(items: Vec<FormatItem<'_>>) -> Self { + items.as_slice().into() + } +} + +impl<'a, T: AsRef<[FormatItem<'a>]> + ?Sized> From<&T> for OwnedFormatItem { + fn from(items: &T) -> Self { + Self::Compound( + items + .as_ref() + .iter() + .cloned() + .map(Into::into) + .collect::<Vec<_>>() + .into_boxed_slice(), + ) + } +} +// endregion conversions from FormatItem + +// region: from variants +impl From<Component> for OwnedFormatItem { + fn from(component: Component) -> Self { + Self::Component(component) + } +} + +impl TryFrom<OwnedFormatItem> for Component { + type Error = error::DifferentVariant; + + fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> { + match value { + OwnedFormatItem::Component(component) => Ok(component), + _ => Err(error::DifferentVariant), + } + } +} + +impl From<Vec<Self>> for OwnedFormatItem { + fn from(items: Vec<Self>) -> Self { + Self::Compound(items.into_boxed_slice()) + } +} + +impl TryFrom<OwnedFormatItem> for Vec<OwnedFormatItem> { + type Error = error::DifferentVariant; + + fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> { + match value { + OwnedFormatItem::Compound(items) => Ok(items.into_vec()), + _ => Err(error::DifferentVariant), + } + } +} +// endregion from variants + +// region: equality +impl PartialEq<Component> for OwnedFormatItem { + fn eq(&self, rhs: &Component) -> bool { + matches!(self, Self::Component(component) if component == rhs) + } +} + +impl PartialEq<OwnedFormatItem> for Component { + fn eq(&self, rhs: &OwnedFormatItem) -> bool { + rhs == self + } +} + +impl PartialEq<&[Self]> for OwnedFormatItem { + fn eq(&self, rhs: &&[Self]) -> bool { + matches!(self, Self::Compound(compound) if &&**compound == rhs) + } +} + +impl PartialEq<OwnedFormatItem> for &[OwnedFormatItem] { + fn eq(&self, rhs: &OwnedFormatItem) -> bool { + rhs == self + } +} +// endregion equality diff --git a/third_party/rust/time/src/format_description/parse/ast.rs b/third_party/rust/time/src/format_description/parse/ast.rs new file mode 100644 index 0000000000..6977cc9cda --- /dev/null +++ b/third_party/rust/time/src/format_description/parse/ast.rs @@ -0,0 +1,278 @@ +//! AST for parsing format descriptions. + +use alloc::string::String; +use alloc::vec::Vec; +use core::iter; +use core::iter::Peekable; + +use super::{lexer, Error, Location, Span}; + +/// One part of a complete format description. +#[allow(variant_size_differences)] +pub(super) enum Item<'a> { + /// A literal string, formatted and parsed as-is. + Literal { + /// The string itself. + value: &'a [u8], + /// Where the string originates from in the format string. + _span: Span, + }, + /// A sequence of brackets. The first acts as the escape character. + EscapedBracket { + /// The first bracket. + _first: Location, + /// The second bracket. + _second: Location, + }, + /// Part of a type, along with its modifiers. + Component { + /// Where the opening bracket was in the format string. + _opening_bracket: Location, + /// Whitespace between the opening bracket and name. + _leading_whitespace: Option<Whitespace<'a>>, + /// The name of the component. + name: Name<'a>, + /// The modifiers for the component. + modifiers: Vec<Modifier<'a>>, + /// Whitespace between the modifiers and closing bracket. + _trailing_whitespace: Option<Whitespace<'a>>, + /// Where the closing bracket was in the format string. + _closing_bracket: Location, + }, +} + +/// Whitespace within a component. +pub(super) struct Whitespace<'a> { + /// The whitespace itself. + pub(super) _value: &'a [u8], + /// Where the whitespace was in the format string. + pub(super) span: Span, +} + +/// The name of a component. +pub(super) struct Name<'a> { + /// The name itself. + pub(super) value: &'a [u8], + /// Where the name was in the format string. + pub(super) span: Span, +} + +/// A modifier for a component. +pub(super) struct Modifier<'a> { + /// Whitespace preceding the modifier. + pub(super) _leading_whitespace: Whitespace<'a>, + /// The key of the modifier. + pub(super) key: Key<'a>, + /// Where the colon of the modifier was in the format string. + pub(super) _colon: Location, + /// The value of the modifier. + pub(super) value: Value<'a>, +} + +/// The key of a modifier. +pub(super) struct Key<'a> { + /// The key itself. + pub(super) value: &'a [u8], + /// Where the key was in the format string. + pub(super) span: Span, +} + +/// The value of a modifier. +pub(super) struct Value<'a> { + /// The value itself. + pub(super) value: &'a [u8], + /// Where the value was in the format string. + pub(super) span: Span, +} + +/// Parse the provided tokens into an AST. +pub(super) fn parse<'a>( + tokens: impl Iterator<Item = lexer::Token<'a>>, +) -> impl Iterator<Item = Result<Item<'a>, Error>> { + let mut tokens = tokens.peekable(); + iter::from_fn(move || { + Some(match tokens.next()? { + lexer::Token::Literal { value, span } => Ok(Item::Literal { value, _span: span }), + lexer::Token::Bracket { + kind: lexer::BracketKind::Opening, + location, + } => { + // escaped bracket + if let Some(&lexer::Token::Bracket { + kind: lexer::BracketKind::Opening, + location: second_location, + }) = tokens.peek() + { + tokens.next(); // consume + Ok(Item::EscapedBracket { + _first: location, + _second: second_location, + }) + } + // component + else { + parse_component(location, &mut tokens) + } + } + lexer::Token::Bracket { + kind: lexer::BracketKind::Closing, + location: _, + } => unreachable!( + "internal error: closing bracket should have been consumed by `parse_component`", + ), + lexer::Token::ComponentPart { + kind: _, + value: _, + span: _, + } => unreachable!( + "internal error: component part should have been consumed by `parse_component`", + ), + }) + }) +} + +/// Parse a component. This assumes that the opening bracket has already been consumed. +fn parse_component<'a>( + opening_bracket: Location, + tokens: &mut Peekable<impl Iterator<Item = lexer::Token<'a>>>, +) -> Result<Item<'a>, Error> { + let leading_whitespace = if let Some(&lexer::Token::ComponentPart { + kind: lexer::ComponentKind::Whitespace, + value, + span, + }) = tokens.peek() + { + tokens.next(); // consume + Some(Whitespace { + _value: value, + span, + }) + } else { + None + }; + + let name = if let Some(&lexer::Token::ComponentPart { + kind: lexer::ComponentKind::NotWhitespace, + value, + span, + }) = tokens.peek() + { + tokens.next(); // consume + Name { value, span } + } else { + let span = leading_whitespace.map_or_else( + || Span { + start: opening_bracket, + end: opening_bracket, + }, + |whitespace| whitespace.span.shrink_to_end(), + ); + return Err(Error { + _inner: span.error("expected component name"), + public: crate::error::InvalidFormatDescription::MissingComponentName { + index: span.start_byte(), + }, + }); + }; + + let mut modifiers = Vec::new(); + let trailing_whitespace = loop { + let whitespace = if let Some(&lexer::Token::ComponentPart { + kind: lexer::ComponentKind::Whitespace, + value, + span, + }) = tokens.peek() + { + tokens.next(); // consume + Whitespace { + _value: value, + span, + } + } else { + break None; + }; + + if let Some(&lexer::Token::ComponentPart { + kind: lexer::ComponentKind::NotWhitespace, + value, + span, + }) = tokens.peek() + { + tokens.next(); // consume + + let colon_index = match value.iter().position(|&b| b == b':') { + Some(index) => index, + None => { + return Err(Error { + _inner: span.error("modifier must be of the form `key:value`"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(value).into_owned(), + index: span.start_byte(), + }, + }); + } + }; + let key = &value[..colon_index]; + let value = &value[colon_index + 1..]; + + if key.is_empty() { + return Err(Error { + _inner: span.shrink_to_start().error("expected modifier key"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::new(), + index: span.start_byte(), + }, + }); + } + if value.is_empty() { + return Err(Error { + _inner: span.shrink_to_end().error("expected modifier value"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::new(), + index: span.shrink_to_end().start_byte(), + }, + }); + } + + modifiers.push(Modifier { + _leading_whitespace: whitespace, + key: Key { + value: key, + span: span.subspan(..colon_index), + }, + _colon: span.start.offset(colon_index), + value: Value { + value, + span: span.subspan(colon_index + 1..), + }, + }); + } else { + break Some(whitespace); + } + }; + + let closing_bracket = if let Some(&lexer::Token::Bracket { + kind: lexer::BracketKind::Closing, + location, + }) = tokens.peek() + { + tokens.next(); // consume + location + } else { + return Err(Error { + _inner: opening_bracket.error("unclosed bracket"), + public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { + index: opening_bracket.byte, + }, + }); + }; + + Ok(Item::Component { + _opening_bracket: opening_bracket, + _leading_whitespace: leading_whitespace, + name, + modifiers, + _trailing_whitespace: trailing_whitespace, + _closing_bracket: closing_bracket, + }) +} diff --git a/third_party/rust/time/src/format_description/parse/format_item.rs b/third_party/rust/time/src/format_description/parse/format_item.rs new file mode 100644 index 0000000000..53146d5228 --- /dev/null +++ b/third_party/rust/time/src/format_description/parse/format_item.rs @@ -0,0 +1,386 @@ +//! Typed, validated representation of a parsed format description. + +use alloc::string::String; + +use super::{ast, Error}; + +/// Parse an AST iterator into a sequence of format items. +pub(super) fn parse<'a>( + ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>, +) -> impl Iterator<Item = Result<Item<'a>, Error>> { + ast_items.map(|ast_item| ast_item.and_then(Item::from_ast)) +} + +/// A description of how to format and parse one part of a type. +#[allow(variant_size_differences)] +pub(super) enum Item<'a> { + /// A literal string. + Literal(&'a [u8]), + /// Part of a type, along with its modifiers. + Component(Component), +} + +impl Item<'_> { + /// Parse an AST item into a format item. + pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> { + Ok(match ast_item { + ast::Item::Component { + _opening_bracket: _, + _leading_whitespace: _, + name, + modifiers, + _trailing_whitespace: _, + _closing_bracket: _, + } => Item::Component(component_from_ast(&name, &modifiers)?), + ast::Item::Literal { value, _span: _ } => Item::Literal(value), + ast::Item::EscapedBracket { + _first: _, + _second: _, + } => Item::Literal(b"["), + }) + } +} + +impl<'a> From<Item<'a>> for crate::format_description::FormatItem<'a> { + fn from(item: Item<'a>) -> Self { + match item { + Item::Literal(literal) => Self::Literal(literal), + Item::Component(component) => Self::Component(component.into()), + } + } +} + +impl From<Item<'_>> for crate::format_description::OwnedFormatItem { + fn from(item: Item<'_>) -> Self { + match item { + Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()), + Item::Component(component) => Self::Component(component.into()), + } + } +} + +/// Declare the `Component` struct. +macro_rules! component_definition { + ($vis:vis enum $name:ident { + $($variant:ident = $parse_variant:literal { + $($field:ident = $parse_field:literal: + Option<$field_type:ty> => $target_field:ident),* $(,)? + }),* $(,)? + }) => { + $vis enum $name { + $($variant($variant),)* + } + + $($vis struct $variant { + $($field: Option<$field_type>),* + })* + + $(impl $variant { + /// Parse the component from the AST, given its modifiers. + fn with_modifiers(modifiers: &[ast::Modifier<'_>]) -> Result<Self, Error> { + let mut this = Self { + $($field: None),* + }; + + for modifier in modifiers { + $(if modifier.key.value.eq_ignore_ascii_case($parse_field) { + this.$field = <$field_type>::from_modifier_value(&modifier.value)?; + continue; + })* + return Err(Error { + _inner: modifier.key.span.error("invalid modifier key"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(modifier.key.value).into_owned(), + index: modifier.key.span.start_byte(), + } + }); + } + + Ok(this) + } + })* + + impl From<$name> for crate::format_description::Component { + fn from(component: $name) -> Self { + match component {$( + $name::$variant($variant { $($field),* }) => { + $crate::format_description::component::Component::$variant( + $crate::format_description::modifier::$variant {$( + $target_field: $field.unwrap_or_default().into() + ),*} + ) + } + )*} + } + } + + /// Parse a component from the AST, given its name and modifiers. + fn component_from_ast( + name: &ast::Name<'_>, + modifiers: &[ast::Modifier<'_>], + ) -> Result<Component, Error> { + $(if name.value.eq_ignore_ascii_case($parse_variant) { + return Ok(Component::$variant($variant::with_modifiers(&modifiers)?)); + })* + Err(Error { + _inner: name.span.error("invalid component"), + public: crate::error::InvalidFormatDescription::InvalidComponentName { + name: String::from_utf8_lossy(name.value).into_owned(), + index: name.span.start_byte(), + }, + }) + } + } +} + +// Keep in alphabetical order. +component_definition! { + pub(super) enum Component { + Day = b"day" { + padding = b"padding": Option<Padding> => padding, + }, + Hour = b"hour" { + padding = b"padding": Option<Padding> => padding, + base = b"repr": Option<HourBase> => is_12_hour_clock, + }, + Minute = b"minute" { + padding = b"padding": Option<Padding> => padding, + }, + Month = b"month" { + padding = b"padding": Option<Padding> => padding, + repr = b"repr": Option<MonthRepr> => repr, + case_sensitive = b"case_sensitive": Option<MonthCaseSensitive> => case_sensitive, + }, + OffsetHour = b"offset_hour" { + sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory, + padding = b"padding": Option<Padding> => padding, + }, + OffsetMinute = b"offset_minute" { + padding = b"padding": Option<Padding> => padding, + }, + OffsetSecond = b"offset_second" { + padding = b"padding": Option<Padding> => padding, + }, + Ordinal = b"ordinal" { + padding = b"padding": Option<Padding> => padding, + }, + Period = b"period" { + case = b"case": Option<PeriodCase> => is_uppercase, + case_sensitive = b"case_sensitive": Option<PeriodCaseSensitive> => case_sensitive, + }, + Second = b"second" { + padding = b"padding": Option<Padding> => padding, + }, + Subsecond = b"subsecond" { + digits = b"digits": Option<SubsecondDigits> => digits, + }, + Weekday = b"weekday" { + repr = b"repr": Option<WeekdayRepr> => repr, + one_indexed = b"one_indexed": Option<WeekdayOneIndexed> => one_indexed, + case_sensitive = b"case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive, + }, + WeekNumber = b"week_number" { + padding = b"padding": Option<Padding> => padding, + repr = b"repr": Option<WeekNumberRepr> => repr, + }, + Year = b"year" { + padding = b"padding": Option<Padding> => padding, + repr = b"repr": Option<YearRepr> => repr, + base = b"base": Option<YearBase> => iso_week_based, + sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory, + }, + } +} + +/// Get the target type for a given enum. +macro_rules! target_ty { + ($name:ident $type:ty) => { + $type + }; + ($name:ident) => { + $crate::format_description::modifier::$name + }; +} + +/// Get the target value for a given enum. +macro_rules! target_value { + ($name:ident $variant:ident $value:expr) => { + $value + }; + ($name:ident $variant:ident) => { + $crate::format_description::modifier::$name::$variant + }; +} + +// TODO use `#[derive(Default)]` on enums once MSRV is 1.62 (NET 2022-12-30) +/// Simulate `#[derive(Default)]` on enums. +macro_rules! derived_default_on_enum { + ($type:ty; $default:expr) => {}; + ($attr:meta $type:ty; $default:expr) => { + impl Default for $type { + fn default() -> Self { + $default + } + } + }; +} + +/// Declare the various modifiers. +/// +/// For the general case, ordinary syntax can be used. Note that you _must_ declare a default +/// variant. The only significant change is that the string representation of the variant must be +/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant +/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be +/// used when parsing the modifier. The value is not case sensitive. +/// +/// If the type in the public API does not have the same name as the type in the internal +/// representation, then the former must be specified in parenthesis after the internal name. For +/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in +/// the public API. +/// +/// By default, the internal variant name is assumed to be the same as the public variant name. If +/// this is not the case, the qualified path to the variant must be specified in parenthesis after +/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve", +/// but is represented as `true` in the public API. +macro_rules! modifier { + ($( + enum $name:ident $(($target_ty:ty))? { + $( + $(#[$attr:meta])? + $variant:ident $(($target_value:expr))? = $parse_variant:literal + ),* $(,)? + } + )+) => {$( + enum $name { + $($variant),* + } + + $(derived_default_on_enum! { + $($attr)? $name; $name::$variant + })* + + impl $name { + /// Parse the modifier from its string representation. + fn from_modifier_value(value: &ast::Value<'_>) -> Result<Option<Self>, Error> { + $(if value.value.eq_ignore_ascii_case($parse_variant) { + return Ok(Some(Self::$variant)); + })* + Err(Error { + _inner: value.span.error("invalid modifier value"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(value.value).into_owned(), + index: value.span.start_byte(), + }, + }) + } + } + + impl From<$name> for target_ty!($name $($target_ty)?) { + fn from(modifier: $name) -> Self { + match modifier { + $($name::$variant => target_value!($name $variant $($target_value)?)),* + } + } + } + )+}; +} + +// Keep in alphabetical order. +modifier! { + enum HourBase(bool) { + Twelve(true) = b"12", + #[default] + TwentyFour(false) = b"24", + } + + enum MonthCaseSensitive(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum MonthRepr { + #[default] + Numerical = b"numerical", + Long = b"long", + Short = b"short", + } + + enum Padding { + Space = b"space", + #[default] + Zero = b"zero", + None = b"none", + } + + enum PeriodCase(bool) { + Lower(false) = b"lower", + #[default] + Upper(true) = b"upper", + } + + enum PeriodCaseSensitive(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum SignBehavior(bool) { + #[default] + Automatic(false) = b"automatic", + Mandatory(true) = b"mandatory", + } + + enum SubsecondDigits { + One = b"1", + Two = b"2", + Three = b"3", + Four = b"4", + Five = b"5", + Six = b"6", + Seven = b"7", + Eight = b"8", + Nine = b"9", + #[default] + OneOrMore = b"1+", + } + + enum WeekNumberRepr { + #[default] + Iso = b"iso", + Sunday = b"sunday", + Monday = b"monday", + } + + enum WeekdayCaseSensitive(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum WeekdayOneIndexed(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum WeekdayRepr { + Short = b"short", + #[default] + Long = b"long", + Sunday = b"sunday", + Monday = b"monday", + } + + enum YearBase(bool) { + #[default] + Calendar(false) = b"calendar", + IsoWeek(true) = b"iso_week", + } + + enum YearRepr { + #[default] + Full = b"full", + LastTwo = b"last_two", + } +} diff --git a/third_party/rust/time/src/format_description/parse/lexer.rs b/third_party/rust/time/src/format_description/parse/lexer.rs new file mode 100644 index 0000000000..e405ea8c85 --- /dev/null +++ b/third_party/rust/time/src/format_description/parse/lexer.rs @@ -0,0 +1,159 @@ +//! Lexer for parsing format descriptions. + +use core::iter; + +use super::{Location, Span}; + +/// A token emitted by the lexer. There is no semantic meaning at this stage. +pub(super) enum Token<'a> { + /// A literal string, formatted and parsed as-is. + Literal { + /// The string itself. + value: &'a [u8], + /// Where the string was in the format string. + span: Span, + }, + /// An opening or closing bracket. May or may not be the start or end of a component. + Bracket { + /// Whether the bracket is opening or closing. + kind: BracketKind, + /// Where the bracket was in the format string. + location: Location, + }, + /// One part of a component. This could be its name, a modifier, or whitespace. + ComponentPart { + /// Whether the part is whitespace or not. + kind: ComponentKind, + /// The part itself. + value: &'a [u8], + /// Where the part was in the format string. + span: Span, + }, +} + +/// What type of bracket is present. +pub(super) enum BracketKind { + /// An opening bracket: `[` + Opening, + /// A closing bracket: `]` + Closing, +} + +/// Indicates whether the component is whitespace or not. +pub(super) enum ComponentKind { + #[allow(clippy::missing_docs_in_private_items)] + Whitespace, + #[allow(clippy::missing_docs_in_private_items)] + NotWhitespace, +} + +/// Attach [`Location`] information to each byte in the iterator. +fn attach_location(iter: impl Iterator<Item = u8>) -> impl Iterator<Item = (u8, Location)> { + let mut line = 1; + let mut column = 1; + let mut byte_pos = 0; + + iter.map(move |byte| { + let location = Location { + line, + column, + byte: byte_pos, + }; + column += 1; + byte_pos += 1; + + if byte == b'\n' { + line += 1; + column = 1; + } + + (byte, location) + }) +} + +/// Parse the string into a series of [`Token`]s. +pub(super) fn lex(mut input: &[u8]) -> impl Iterator<Item = Token<'_>> { + let mut depth: u8 = 0; + let mut iter = attach_location(input.iter().copied()).peekable(); + let mut second_bracket_location = None; + + iter::from_fn(move || { + // There is a flag set to emit the second half of an escaped bracket pair. + if let Some(location) = second_bracket_location.take() { + return Some(Token::Bracket { + kind: BracketKind::Opening, + location, + }); + } + + Some(match iter.next()? { + (b'[', location) => { + if let Some((_, second_location)) = iter.next_if(|&(byte, _)| byte == b'[') { + // escaped bracket + second_bracket_location = Some(second_location); + input = &input[2..]; + } else { + // opening bracket + depth += 1; + input = &input[1..]; + } + + Token::Bracket { + kind: BracketKind::Opening, + location, + } + } + // closing bracket + (b']', location) if depth > 0 => { + depth -= 1; + input = &input[1..]; + Token::Bracket { + kind: BracketKind::Closing, + location, + } + } + // literal + (_, start_location) if depth == 0 => { + let mut bytes = 1; + let mut end_location = start_location; + + while let Some((_, location)) = iter.next_if(|&(byte, _)| byte != b'[') { + end_location = location; + bytes += 1; + } + + let value = &input[..bytes]; + input = &input[bytes..]; + Token::Literal { + value, + span: Span::start_end(start_location, end_location), + } + } + // component part + (byte, start_location) => { + let mut bytes = 1; + let mut end_location = start_location; + let is_whitespace = byte.is_ascii_whitespace(); + + while let Some((_, location)) = iter.next_if(|&(byte, _)| { + byte != b'[' && byte != b']' && is_whitespace == byte.is_ascii_whitespace() + }) { + end_location = location; + bytes += 1; + } + + let value = &input[..bytes]; + input = &input[bytes..]; + Token::ComponentPart { + kind: if is_whitespace { + ComponentKind::Whitespace + } else { + ComponentKind::NotWhitespace + }, + value, + span: Span::start_end(start_location, end_location), + } + } + }) + }) +} diff --git a/third_party/rust/time/src/format_description/parse/mod.rs b/third_party/rust/time/src/format_description/parse/mod.rs new file mode 100644 index 0000000000..c73a674494 --- /dev/null +++ b/third_party/rust/time/src/format_description/parse/mod.rs @@ -0,0 +1,193 @@ +//! Parser for format descriptions. + +use alloc::vec::Vec; +use core::ops::{RangeFrom, RangeTo}; + +mod ast; +mod format_item; +mod lexer; + +/// Parse a sequence of items from the format description. +/// +/// The syntax for the format description can be found in [the +/// book](https://time-rs.github.io/book/api/format-description.html). +pub fn parse( + s: &str, +) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription> +{ + let lexed = lexer::lex(s.as_bytes()); + let ast = ast::parse(lexed); + let format_items = format_item::parse(ast); + Ok(format_items + .map(|res| res.map(Into::into)) + .collect::<Result<Vec<_>, _>>()?) +} + +/// Parse a sequence of items from the format description. +/// +/// The syntax for the format description can be found in [the +/// book](https://time-rs.github.io/book/api/format-description.html). +/// +/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means +/// that there is no lifetime that needs to be handled. +/// +/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem +pub fn parse_owned( + s: &str, +) -> Result<crate::format_description::OwnedFormatItem, crate::error::InvalidFormatDescription> { + let lexed = lexer::lex(s.as_bytes()); + let ast = ast::parse(lexed); + let format_items = format_item::parse(ast); + let items = format_items + .map(|res| res.map(Into::into)) + .collect::<Result<Vec<_>, _>>()? + .into_boxed_slice(); + Ok(crate::format_description::OwnedFormatItem::Compound(items)) +} + +/// A location within a string. +#[derive(Clone, Copy)] +struct Location { + /// The one-indexed line of the string. + line: usize, + /// The one-indexed column of the string. + column: usize, + /// The zero-indexed byte of the string. + byte: usize, +} + +impl Location { + /// Offset the location by the provided amount. + /// + /// Note that this assumes the resulting location is on the same line as the original location. + #[must_use = "this does not modify the original value"] + const fn offset(&self, offset: usize) -> Self { + Self { + line: self.line, + column: self.column + offset, + byte: self.byte + offset, + } + } + + /// Create an error with the provided message at this location. + const fn error(self, message: &'static str) -> ErrorInner { + ErrorInner { + _message: message, + _span: Span { + start: self, + end: self, + }, + } + } +} + +/// A start and end point within a string. +#[derive(Clone, Copy)] +struct Span { + #[allow(clippy::missing_docs_in_private_items)] + start: Location, + #[allow(clippy::missing_docs_in_private_items)] + end: Location, +} + +impl Span { + /// Create a new `Span` from the provided start and end locations. + const fn start_end(start: Location, end: Location) -> Self { + Self { start, end } + } + + /// Reduce this span to the provided range. + #[must_use = "this does not modify the original value"] + fn subspan(&self, range: impl Subspan) -> Self { + range.subspan(self) + } + + /// Obtain a `Span` pointing at the start of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_start(&self) -> Self { + Self { + start: self.start, + end: self.start, + } + } + + /// Obtain a `Span` pointing at the end of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_end(&self) -> Self { + Self { + start: self.end, + end: self.end, + } + } + + /// Create an error with the provided message at this span. + const fn error(self, message: &'static str) -> ErrorInner { + ErrorInner { + _message: message, + _span: self, + } + } + + /// Get the byte index that the span starts at. + const fn start_byte(&self) -> usize { + self.start.byte + } +} + +/// A trait for types that can be used to reduce a `Span`. +trait Subspan { + /// Reduce the provided `Span` to a new `Span`. + fn subspan(self, span: &Span) -> Span; +} + +impl Subspan for RangeFrom<usize> { + fn subspan(self, span: &Span) -> Span { + assert_eq!(span.start.line, span.end.line); + + Span { + start: Location { + line: span.start.line, + column: span.start.column + self.start, + byte: span.start.byte + self.start, + }, + end: span.end, + } + } +} + +impl Subspan for RangeTo<usize> { + fn subspan(self, span: &Span) -> Span { + assert_eq!(span.start.line, span.end.line); + + Span { + start: span.start, + end: Location { + line: span.start.line, + column: span.start.column + self.end - 1, + byte: span.start.byte + self.end - 1, + }, + } + } +} + +/// The internal error type. +struct ErrorInner { + /// The message displayed to the user. + _message: &'static str, + /// Where the error originated. + _span: Span, +} + +/// A complete error description. +struct Error { + /// The internal error. + _inner: ErrorInner, + /// The error needed for interoperability with the rest of `time`. + public: crate::error::InvalidFormatDescription, +} + +impl From<Error> for crate::error::InvalidFormatDescription { + fn from(error: Error) -> Self { + error.public + } +} diff --git a/third_party/rust/time/src/format_description/well_known/iso8601.rs b/third_party/rust/time/src/format_description/well_known/iso8601.rs new file mode 100644 index 0000000000..f19181a926 --- /dev/null +++ b/third_party/rust/time/src/format_description/well_known/iso8601.rs @@ -0,0 +1,233 @@ +//! The format described in ISO 8601. + +mod adt_hack; + +use core::num::NonZeroU8; + +pub use self::adt_hack::{DoNotRelyOnWhatThisIs, EncodedConfig}; + +/// A configuration for [`Iso8601`] that only parses values. +const PARSING_ONLY: EncodedConfig = Config { + formatted_components: FormattedComponents::None, + use_separators: false, + year_is_six_digits: false, + date_kind: DateKind::Calendar, + time_precision: TimePrecision::Hour { + decimal_digits: None, + }, + offset_precision: OffsetPrecision::Hour, +} +.encode(); + +/// The default configuration for [`Iso8601`]. +const DEFAULT_CONFIG: EncodedConfig = Config::DEFAULT.encode(); + +/// The format described in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html). +/// +/// This implementation is of ISO 8601-1:2019. It may not be compatible with other versions. +/// +/// The const parameter `CONFIG` **must** be a value that was returned by [`Config::encode`]. +/// Passing any other value is **unspecified behavior**. +/// +/// Example: 1997-11-21T09:55:06.000000000-06:00 +/// +/// # Examples +#[cfg_attr(feature = "formatting", doc = "```rust")] +#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")] +/// # use time::format_description::well_known::Iso8601; +/// # use time_macros::datetime; +/// assert_eq!( +/// datetime!(1997-11-12 9:55:06 -6:00).format(&Iso8601::DEFAULT)?, +/// "1997-11-12T09:55:06.000000000-06:00" +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Iso8601<const CONFIG: EncodedConfig = DEFAULT_CONFIG>; + +impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Iso8601") + .field("config", &Config::decode(CONFIG)) + .finish() + } +} + +impl Iso8601<DEFAULT_CONFIG> { + /// An [`Iso8601`] with the default configuration. + /// + /// The following is the default behavior: + /// + /// - The configuration can be used for both formatting and parsing. + /// - The date, time, and UTC offset are all formatted. + /// - Separators (such as `-` and `:`) are included. + /// - The year contains four digits, such that the year must be between 0 and 9999. + /// - The date uses the calendar format. + /// - The time has precision to the second and nine decimal digits. + /// - The UTC offset has precision to the minute. + /// + /// If you need different behavior, use [`Config::DEFAULT`] and [`Config`]'s methods to create + /// a custom configuration. + pub const DEFAULT: Self = Self; +} + +impl Iso8601<PARSING_ONLY> { + /// An [`Iso8601`] that can only be used for parsing. Using this to format a value is + /// unspecified behavior. + pub const PARSING: Self = Self; +} + +/// Which components to format. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FormattedComponents { + /// The configuration can only be used for parsing. Using this to format a value is + /// unspecified behavior. + None, + /// Format only the date. + Date, + /// Format only the time. + Time, + /// Format only the UTC offset. + Offset, + /// Format the date and time. + DateTime, + /// Format the date, time, and UTC offset. + DateTimeOffset, + /// Format the time and UTC offset. + TimeOffset, +} + +/// Which format to use for the date. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DateKind { + /// Use the year-month-day format. + Calendar, + /// Use the year-week-weekday format. + Week, + /// Use the week-ordinal format. + Ordinal, +} + +/// The precision and number of decimal digits present for the time. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TimePrecision { + /// Format the hour only. Minutes, seconds, and nanoseconds will be represented with the + /// specified number of decimal digits, if any. + Hour { + #[allow(clippy::missing_docs_in_private_items)] + decimal_digits: Option<NonZeroU8>, + }, + /// Format the hour and minute. Seconds and nanoseconds will be represented with the specified + /// number of decimal digits, if any. + Minute { + #[allow(clippy::missing_docs_in_private_items)] + decimal_digits: Option<NonZeroU8>, + }, + /// Format the hour, minute, and second. Nanoseconds will be represented with the specified + /// number of decimal digits, if any. + Second { + #[allow(clippy::missing_docs_in_private_items)] + decimal_digits: Option<NonZeroU8>, + }, +} + +/// The precision for the UTC offset. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OffsetPrecision { + /// Format only the offset hour. Requires the offset minute to be zero. + Hour, + /// Format both the offset hour and minute. + Minute, +} + +/// Configuration for [`Iso8601`]. +// This is only used as a const generic, so there's no need to have a number of implementations on +// it. +#[allow(missing_copy_implementations)] +#[doc(alias = "EncodedConfig")] // People will likely search for `EncodedConfig`, so show them this. +#[derive(Debug)] +pub struct Config { + /// Which components, if any, will be formatted. + pub(crate) formatted_components: FormattedComponents, + /// Whether the format contains separators (such as `-` or `:`). + pub(crate) use_separators: bool, + /// Whether the year is six digits. + pub(crate) year_is_six_digits: bool, + /// The format used for the date. + pub(crate) date_kind: DateKind, + /// The precision and number of decimal digits present for the time. + pub(crate) time_precision: TimePrecision, + /// The precision for the UTC offset. + pub(crate) offset_precision: OffsetPrecision, +} + +impl Config { + /// A configuration for the [`Iso8601`] format. + /// + /// The following is the default behavior: + /// + /// - The configuration can be used for both formatting and parsing. + /// - The date, time, and UTC offset are all formatted. + /// - Separators (such as `-` and `:`) are included. + /// - The year contains four digits, such that the year must be between 0 and 9999. + /// - The date uses the calendar format. + /// - The time has precision to the second and nine decimal digits. + /// - The UTC offset has precision to the minute. + /// + /// If you need different behavior, use the setter methods on this struct. + pub const DEFAULT: Self = Self { + formatted_components: FormattedComponents::DateTimeOffset, + use_separators: true, + year_is_six_digits: false, + date_kind: DateKind::Calendar, + time_precision: TimePrecision::Second { + decimal_digits: NonZeroU8::new(9), + }, + offset_precision: OffsetPrecision::Minute, + }; + + /// Set whether the format the date, time, and/or UTC offset. + pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self { + Self { + formatted_components, + ..self + } + } + + /// Set whether the format contains separators (such as `-` or `:`). + pub const fn set_use_separators(self, use_separators: bool) -> Self { + Self { + use_separators, + ..self + } + } + + /// Set whether the year is six digits. + pub const fn set_year_is_six_digits(self, year_is_six_digits: bool) -> Self { + Self { + year_is_six_digits, + ..self + } + } + + /// Set the format used for the date. + pub const fn set_date_kind(self, date_kind: DateKind) -> Self { + Self { date_kind, ..self } + } + + /// Set the precision and number of decimal digits present for the time. + pub const fn set_time_precision(self, time_precision: TimePrecision) -> Self { + Self { + time_precision, + ..self + } + } + + /// Set the precision for the UTC offset. + pub const fn set_offset_precision(self, offset_precision: OffsetPrecision) -> Self { + Self { + offset_precision, + ..self + } + } +} diff --git a/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs b/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs new file mode 100644 index 0000000000..757a68b18f --- /dev/null +++ b/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs @@ -0,0 +1,249 @@ +//! Hackery to work around not being able to use ADTs in const generics on stable. + +use core::num::NonZeroU8; + +#[cfg(feature = "formatting")] +use super::Iso8601; +use super::{Config, DateKind, FormattedComponents as FC, OffsetPrecision, TimePrecision}; + +// This provides a way to include `EncodedConfig` in documentation without displaying the type it is +// aliased to. +#[doc(hidden)] +pub type DoNotRelyOnWhatThisIs = u128; + +/// An encoded [`Config`] that can be used as a const parameter to [`Iso8601`]. +/// +/// The type this is aliased to must not be relied upon. It can change in any release without +/// notice. +pub type EncodedConfig = DoNotRelyOnWhatThisIs; + +#[cfg(feature = "formatting")] +impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> { + /// The user-provided configuration for the ISO 8601 format. + const CONFIG: Config = Config::decode(CONFIG); + /// Whether the date should be formatted. + pub(crate) const FORMAT_DATE: bool = matches!( + Self::CONFIG.formatted_components, + FC::Date | FC::DateTime | FC::DateTimeOffset + ); + /// Whether the time should be formatted. + pub(crate) const FORMAT_TIME: bool = matches!( + Self::CONFIG.formatted_components, + FC::Time | FC::DateTime | FC::DateTimeOffset | FC::TimeOffset + ); + /// Whether the UTC offset should be formatted. + pub(crate) const FORMAT_OFFSET: bool = matches!( + Self::CONFIG.formatted_components, + FC::Offset | FC::DateTimeOffset | FC::TimeOffset + ); + /// Whether the year is six digits. + pub(crate) const YEAR_IS_SIX_DIGITS: bool = Self::CONFIG.year_is_six_digits; + /// Whether the format contains separators (such as `-` or `:`). + pub(crate) const USE_SEPARATORS: bool = Self::CONFIG.use_separators; + /// Which format to use for the date. + pub(crate) const DATE_KIND: DateKind = Self::CONFIG.date_kind; + /// The precision and number of decimal digits to use for the time. + pub(crate) const TIME_PRECISION: TimePrecision = Self::CONFIG.time_precision; + /// The precision for the UTC offset. + pub(crate) const OFFSET_PRECISION: OffsetPrecision = Self::CONFIG.offset_precision; +} + +impl Config { + /// Encode the configuration, permitting it to be used as a const parameter of [`Iso8601`]. + /// + /// The value returned by this method must only be used as a const parameter to [`Iso8601`]. Any + /// other usage is unspecified behavior. + pub const fn encode(&self) -> EncodedConfig { + let mut bytes = [0; EncodedConfig::BITS as usize / 8]; + + bytes[0] = match self.formatted_components { + FC::None => 0, + FC::Date => 1, + FC::Time => 2, + FC::Offset => 3, + FC::DateTime => 4, + FC::DateTimeOffset => 5, + FC::TimeOffset => 6, + }; + bytes[1] = self.use_separators as _; + bytes[2] = self.year_is_six_digits as _; + bytes[3] = match self.date_kind { + DateKind::Calendar => 0, + DateKind::Week => 1, + DateKind::Ordinal => 2, + }; + bytes[4] = match self.time_precision { + TimePrecision::Hour { .. } => 0, + TimePrecision::Minute { .. } => 1, + TimePrecision::Second { .. } => 2, + }; + bytes[5] = match self.time_precision { + TimePrecision::Hour { decimal_digits } + | TimePrecision::Minute { decimal_digits } + | TimePrecision::Second { decimal_digits } => match decimal_digits { + None => 0, + Some(decimal_digits) => decimal_digits.get(), + }, + }; + bytes[6] = match self.offset_precision { + OffsetPrecision::Hour => 0, + OffsetPrecision::Minute => 1, + }; + + EncodedConfig::from_be_bytes(bytes) + } + + /// Decode the configuration. The configuration must have been generated from + /// [`Config::encode`]. + pub(super) const fn decode(encoded: EncodedConfig) -> Self { + let bytes = encoded.to_be_bytes(); + + let formatted_components = match bytes[0] { + 0 => FC::None, + 1 => FC::Date, + 2 => FC::Time, + 3 => FC::Offset, + 4 => FC::DateTime, + 5 => FC::DateTimeOffset, + 6 => FC::TimeOffset, + _ => panic!("invalid configuration"), + }; + let use_separators = match bytes[1] { + 0 => false, + 1 => true, + _ => panic!("invalid configuration"), + }; + let year_is_six_digits = match bytes[2] { + 0 => false, + 1 => true, + _ => panic!("invalid configuration"), + }; + let date_kind = match bytes[3] { + 0 => DateKind::Calendar, + 1 => DateKind::Week, + 2 => DateKind::Ordinal, + _ => panic!("invalid configuration"), + }; + let time_precision = match bytes[4] { + 0 => TimePrecision::Hour { + decimal_digits: NonZeroU8::new(bytes[5]), + }, + 1 => TimePrecision::Minute { + decimal_digits: NonZeroU8::new(bytes[5]), + }, + 2 => TimePrecision::Second { + decimal_digits: NonZeroU8::new(bytes[5]), + }, + _ => panic!("invalid configuration"), + }; + let offset_precision = match bytes[6] { + 0 => OffsetPrecision::Hour, + 1 => OffsetPrecision::Minute, + _ => panic!("invalid configuration"), + }; + + // No `for` loops in `const fn`. + let mut idx = 7; // first unused byte + while idx < EncodedConfig::BITS as usize / 8 { + assert!(bytes[idx] == 0, "invalid configuration"); + idx += 1; + } + + Self { + formatted_components, + use_separators, + year_is_six_digits, + date_kind, + time_precision, + offset_precision, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! eq { + ($a:expr, $b:expr) => {{ + let a = $a; + let b = $b; + a.formatted_components == b.formatted_components + && a.use_separators == b.use_separators + && a.year_is_six_digits == b.year_is_six_digits + && a.date_kind == b.date_kind + && a.time_precision == b.time_precision + && a.offset_precision == b.offset_precision + }}; + } + + #[test] + fn encoding_roundtrip() { + macro_rules! assert_roundtrip { + ($config:expr) => { + let config = $config; + let encoded = config.encode(); + let decoded = Config::decode(encoded); + assert!(eq!(config, decoded)); + }; + } + + assert_roundtrip!(Config::DEFAULT); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::None)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Date)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Time)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Offset)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTime)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::TimeOffset)); + assert_roundtrip!(Config::DEFAULT.set_use_separators(false)); + assert_roundtrip!(Config::DEFAULT.set_use_separators(true)); + assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(false)); + assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(true)); + assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Calendar)); + assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Week)); + assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Ordinal)); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour { + decimal_digits: None, + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute { + decimal_digits: None, + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second { + decimal_digits: None, + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour { + decimal_digits: NonZeroU8::new(1), + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute { + decimal_digits: NonZeroU8::new(1), + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second { + decimal_digits: NonZeroU8::new(1), + })); + assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour)); + assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute)); + } + + macro_rules! assert_decode_fail { + ($encoding:expr) => { + assert!( + std::panic::catch_unwind(|| { + Config::decode($encoding); + }) + .is_err() + ); + }; + } + + #[test] + fn decode_fail() { + assert_decode_fail!(0x07_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_02_00_00_00_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_02_00_00_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_00_03_00_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_00_00_03_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_00_00_00_00_02_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_00_00_00_00_00_01_00_00_00_00_00_00_00_00); + } +} diff --git a/third_party/rust/time/src/format_description/well_known/rfc2822.rs b/third_party/rust/time/src/format_description/well_known/rfc2822.rs new file mode 100644 index 0000000000..3c890ab107 --- /dev/null +++ b/third_party/rust/time/src/format_description/well_known/rfc2822.rs @@ -0,0 +1,30 @@ +//! The format described in RFC 2822. + +/// The format described in [RFC 2822](https://tools.ietf.org/html/rfc2822#section-3.3). +/// +/// Example: Fri, 21 Nov 1997 09:55:06 -0600 +/// +/// # Examples +#[cfg_attr(feature = "parsing", doc = "```rust")] +#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")] +/// # use time::{format_description::well_known::Rfc2822, OffsetDateTime}; +/// use time_macros::datetime; +/// assert_eq!( +/// OffsetDateTime::parse("Sat, 12 Jun 1993 13:25:19 GMT", &Rfc2822)?, +/// datetime!(1993-06-12 13:25:19 +00:00) +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +/// +#[cfg_attr(feature = "formatting", doc = "```rust")] +#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")] +/// # use time::format_description::well_known::Rfc2822; +/// # use time_macros::datetime; +/// assert_eq!( +/// datetime!(1997-11-21 09:55:06 -06:00).format(&Rfc2822)?, +/// "Fri, 21 Nov 1997 09:55:06 -0600" +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rfc2822; diff --git a/third_party/rust/time/src/format_description/well_known/rfc3339.rs b/third_party/rust/time/src/format_description/well_known/rfc3339.rs new file mode 100644 index 0000000000..f0873cbac5 --- /dev/null +++ b/third_party/rust/time/src/format_description/well_known/rfc3339.rs @@ -0,0 +1,30 @@ +//! The format described in RFC 3339. + +/// The format described in [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6). +/// +/// Format example: 1985-04-12T23:20:50.52Z +/// +/// # Examples +#[cfg_attr(feature = "parsing", doc = "```rust")] +#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")] +/// # use time::{format_description::well_known::Rfc3339, OffsetDateTime}; +/// # use time_macros::datetime; +/// assert_eq!( +/// OffsetDateTime::parse("1985-04-12T23:20:50.52Z", &Rfc3339)?, +/// datetime!(1985-04-12 23:20:50.52 +00:00) +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +/// +#[cfg_attr(feature = "formatting", doc = "```rust")] +#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")] +/// # use time::format_description::well_known::Rfc3339; +/// # use time_macros::datetime; +/// assert_eq!( +/// datetime!(1985-04-12 23:20:50.52 +00:00).format(&Rfc3339)?, +/// "1985-04-12T23:20:50.52Z" +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rfc3339; diff --git a/third_party/rust/time/src/formatting/formattable.rs b/third_party/rust/time/src/formatting/formattable.rs new file mode 100644 index 0000000000..7fee2fbeab --- /dev/null +++ b/third_party/rust/time/src/formatting/formattable.rs @@ -0,0 +1,304 @@ +//! A trait that can be used to format an item from its components. + +use core::ops::Deref; +use std::io; + +use crate::format_description::well_known::iso8601::EncodedConfig; +use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339}; +use crate::format_description::{FormatItem, OwnedFormatItem}; +use crate::formatting::{ + format_component, format_number_pad_zero, iso8601, write, MONTH_NAMES, WEEKDAY_NAMES, +}; +use crate::{error, Date, Time, UtcOffset}; + +/// A type that can be formatted. +#[cfg_attr(__time_03_docs, doc(notable_trait))] +pub trait Formattable: sealed::Sealed {} +impl Formattable for FormatItem<'_> {} +impl Formattable for [FormatItem<'_>] {} +impl Formattable for OwnedFormatItem {} +impl Formattable for [OwnedFormatItem] {} +impl Formattable for Rfc3339 {} +impl Formattable for Rfc2822 {} +impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {} +impl<T: Deref> Formattable for T where T::Target: Formattable {} + +/// Seal the trait to prevent downstream users from implementing it. +mod sealed { + #[allow(clippy::wildcard_imports)] + use super::*; + + /// Format the item using a format description, the intended output, and the various components. + pub trait Sealed { + /// Format the item into the provided output, returning the number of bytes written. + fn format_into( + &self, + output: &mut impl io::Write, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<usize, error::Format>; + + /// Format the item directly to a `String`. + fn format( + &self, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<String, error::Format> { + let mut buf = Vec::new(); + self.format_into(&mut buf, date, time, offset)?; + Ok(String::from_utf8_lossy(&buf).into_owned()) + } + } +} + +// region: custom formats +impl<'a> sealed::Sealed for FormatItem<'a> { + fn format_into( + &self, + output: &mut impl io::Write, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<usize, error::Format> { + Ok(match *self { + Self::Literal(literal) => write(output, literal)?, + Self::Component(component) => format_component(output, component, date, time, offset)?, + Self::Compound(items) => items.format_into(output, date, time, offset)?, + Self::Optional(item) => item.format_into(output, date, time, offset)?, + Self::First(items) => match items { + [] => 0, + [item, ..] => item.format_into(output, date, time, offset)?, + }, + }) + } +} + +impl<'a> sealed::Sealed for [FormatItem<'a>] { + fn format_into( + &self, + output: &mut impl io::Write, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<usize, error::Format> { + let mut bytes = 0; + for item in self.iter() { + bytes += item.format_into(output, date, time, offset)?; + } + Ok(bytes) + } +} + +impl sealed::Sealed for OwnedFormatItem { + fn format_into( + &self, + output: &mut impl io::Write, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<usize, error::Format> { + match self { + Self::Literal(literal) => Ok(write(output, literal)?), + Self::Component(component) => format_component(output, *component, date, time, offset), + Self::Compound(items) => items.format_into(output, date, time, offset), + Self::Optional(item) => item.format_into(output, date, time, offset), + Self::First(items) => match &**items { + [] => Ok(0), + [item, ..] => item.format_into(output, date, time, offset), + }, + } + } +} + +impl sealed::Sealed for [OwnedFormatItem] { + fn format_into( + &self, + output: &mut impl io::Write, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<usize, error::Format> { + let mut bytes = 0; + for item in self.iter() { + bytes += item.format_into(output, date, time, offset)?; + } + Ok(bytes) + } +} + +impl<T: Deref> sealed::Sealed for T +where + T::Target: sealed::Sealed, +{ + fn format_into( + &self, + output: &mut impl io::Write, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<usize, error::Format> { + self.deref().format_into(output, date, time, offset) + } +} +// endregion custom formats + +// region: well-known formats +impl sealed::Sealed for Rfc2822 { + fn format_into( + &self, + output: &mut impl io::Write, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<usize, error::Format> { + let date = date.ok_or(error::Format::InsufficientTypeInformation)?; + let time = time.ok_or(error::Format::InsufficientTypeInformation)?; + let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?; + + let mut bytes = 0; + + let (year, month, day) = date.to_calendar_date(); + + if year < 1900 { + return Err(error::Format::InvalidComponent("year")); + } + if offset.seconds_past_minute() != 0 { + return Err(error::Format::InvalidComponent("offset_second")); + } + + bytes += write( + output, + &WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3], + )?; + bytes += write(output, b", ")?; + bytes += format_number_pad_zero::<2, _, _>(output, day)?; + bytes += write(output, b" ")?; + bytes += write(output, &MONTH_NAMES[month as usize - 1][..3])?; + bytes += write(output, b" ")?; + bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + bytes += write(output, b" ")?; + bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?; + bytes += write(output, b":")?; + bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?; + bytes += write(output, b":")?; + bytes += format_number_pad_zero::<2, _, _>(output, time.second())?; + bytes += write(output, b" ")?; + bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?; + bytes += format_number_pad_zero::<2, _, _>(output, offset.whole_hours().unsigned_abs())?; + bytes += + format_number_pad_zero::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs())?; + + Ok(bytes) + } +} + +impl sealed::Sealed for Rfc3339 { + fn format_into( + &self, + output: &mut impl io::Write, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<usize, error::Format> { + let date = date.ok_or(error::Format::InsufficientTypeInformation)?; + let time = time.ok_or(error::Format::InsufficientTypeInformation)?; + let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?; + + let mut bytes = 0; + + let year = date.year(); + + if !(0..10_000).contains(&year) { + return Err(error::Format::InvalidComponent("year")); + } + if offset.seconds_past_minute() != 0 { + return Err(error::Format::InvalidComponent("offset_second")); + } + + bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + bytes += write(output, b"-")?; + bytes += format_number_pad_zero::<2, _, _>(output, date.month() as u8)?; + bytes += write(output, b"-")?; + bytes += format_number_pad_zero::<2, _, _>(output, date.day())?; + bytes += write(output, b"T")?; + bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?; + bytes += write(output, b":")?; + bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?; + bytes += write(output, b":")?; + bytes += format_number_pad_zero::<2, _, _>(output, time.second())?; + + #[allow(clippy::if_not_else)] + if time.nanosecond() != 0 { + let nanos = time.nanosecond(); + bytes += write(output, b".")?; + bytes += if nanos % 10 != 0 { + format_number_pad_zero::<9, _, _>(output, nanos) + } else if (nanos / 10) % 10 != 0 { + format_number_pad_zero::<8, _, _>(output, nanos / 10) + } else if (nanos / 100) % 10 != 0 { + format_number_pad_zero::<7, _, _>(output, nanos / 100) + } else if (nanos / 1_000) % 10 != 0 { + format_number_pad_zero::<6, _, _>(output, nanos / 1_000) + } else if (nanos / 10_000) % 10 != 0 { + format_number_pad_zero::<5, _, _>(output, nanos / 10_000) + } else if (nanos / 100_000) % 10 != 0 { + format_number_pad_zero::<4, _, _>(output, nanos / 100_000) + } else if (nanos / 1_000_000) % 10 != 0 { + format_number_pad_zero::<3, _, _>(output, nanos / 1_000_000) + } else if (nanos / 10_000_000) % 10 != 0 { + format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000) + } else { + format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000) + }?; + } + + if offset == UtcOffset::UTC { + bytes += write(output, b"Z")?; + return Ok(bytes); + } + + bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?; + bytes += format_number_pad_zero::<2, _, _>(output, offset.whole_hours().unsigned_abs())?; + bytes += write(output, b":")?; + bytes += + format_number_pad_zero::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs())?; + + Ok(bytes) + } +} + +impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> { + fn format_into( + &self, + output: &mut impl io::Write, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, + ) -> Result<usize, error::Format> { + let mut bytes = 0; + + if Self::FORMAT_DATE { + let date = date.ok_or(error::Format::InsufficientTypeInformation)?; + bytes += iso8601::format_date::<_, CONFIG>(output, date)?; + } + if Self::FORMAT_TIME { + let time = time.ok_or(error::Format::InsufficientTypeInformation)?; + bytes += iso8601::format_time::<_, CONFIG>(output, time)?; + } + if Self::FORMAT_OFFSET { + let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?; + bytes += iso8601::format_offset::<_, CONFIG>(output, offset)?; + } + + if bytes == 0 { + // The only reason there would be no bytes written is if the format was only for + // parsing. + panic!("attempted to format a parsing-only format description"); + } + + Ok(bytes) + } +} +// endregion well-known formats diff --git a/third_party/rust/time/src/formatting/iso8601.rs b/third_party/rust/time/src/formatting/iso8601.rs new file mode 100644 index 0000000000..1724f96f5c --- /dev/null +++ b/third_party/rust/time/src/formatting/iso8601.rs @@ -0,0 +1,139 @@ +//! Helpers for implementing formatting for ISO 8601. + +use std::io; + +use crate::format_description::well_known::iso8601::{ + DateKind, EncodedConfig, OffsetPrecision, TimePrecision, +}; +use crate::format_description::well_known::Iso8601; +use crate::formatting::{format_float, format_number_pad_zero, write, write_if, write_if_else}; +use crate::{error, Date, Time, UtcOffset}; + +/// Format the date portion of ISO 8601. +pub(super) fn format_date<W: io::Write, const CONFIG: EncodedConfig>( + output: &mut W, + date: Date, +) -> Result<usize, error::Format> { + let mut bytes = 0; + + match Iso8601::<CONFIG>::DATE_KIND { + DateKind::Calendar => { + let (year, month, day) = date.to_calendar_date(); + if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS { + bytes += write_if_else(output, year < 0, b"-", b"+")?; + bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?; + } else if !(0..=9999).contains(&year) { + return Err(error::Format::InvalidComponent("year")); + } else { + bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + } + bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?; + bytes += format_number_pad_zero::<2, _, _>(output, month as u8)?; + bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?; + bytes += format_number_pad_zero::<2, _, _>(output, day)?; + } + DateKind::Week => { + let (year, week, day) = date.to_iso_week_date(); + if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS { + bytes += write_if_else(output, year < 0, b"-", b"+")?; + bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?; + } else if !(0..=9999).contains(&year) { + return Err(error::Format::InvalidComponent("year")); + } else { + bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + } + bytes += write_if_else(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-W", b"W")?; + bytes += format_number_pad_zero::<2, _, _>(output, week)?; + bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?; + bytes += format_number_pad_zero::<1, _, _>(output, day.number_from_monday())?; + } + DateKind::Ordinal => { + let (year, day) = date.to_ordinal_date(); + if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS { + bytes += write_if_else(output, year < 0, b"-", b"+")?; + bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?; + } else if !(0..=9999).contains(&year) { + return Err(error::Format::InvalidComponent("year")); + } else { + bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + } + bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?; + bytes += format_number_pad_zero::<3, _, _>(output, day)?; + } + } + + Ok(bytes) +} + +/// Format the time portion of ISO 8601. +pub(super) fn format_time<W: io::Write, const CONFIG: EncodedConfig>( + output: &mut W, + time: Time, +) -> Result<usize, error::Format> { + let mut bytes = 0; + + // The "T" can only be omitted in extended format where there is no date being formatted. + bytes += write_if( + output, + Iso8601::<CONFIG>::USE_SEPARATORS || Iso8601::<CONFIG>::FORMAT_DATE, + b"T", + )?; + + let (hours, minutes, seconds, nanoseconds) = time.as_hms_nano(); + + match Iso8601::<CONFIG>::TIME_PRECISION { + TimePrecision::Hour { decimal_digits } => { + let hours = (hours as f64) + + (minutes as f64) / 60. + + (seconds as f64) / 3_600. + + (nanoseconds as f64) / 3_600. / 1_000_000_000.; + format_float(output, hours, 2, decimal_digits)?; + } + TimePrecision::Minute { decimal_digits } => { + bytes += format_number_pad_zero::<2, _, _>(output, hours)?; + bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?; + let minutes = (minutes as f64) + + (seconds as f64) / 60. + + (nanoseconds as f64) / 60. / 1_000_000_000.; + bytes += format_float(output, minutes, 2, decimal_digits)?; + } + TimePrecision::Second { decimal_digits } => { + bytes += format_number_pad_zero::<2, _, _>(output, hours)?; + bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?; + bytes += format_number_pad_zero::<2, _, _>(output, minutes)?; + bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?; + let seconds = (seconds as f64) + (nanoseconds as f64) / 1_000_000_000.; + bytes += format_float(output, seconds, 2, decimal_digits)?; + } + } + + Ok(bytes) +} + +/// Format the UTC offset portion of ISO 8601. +pub(super) fn format_offset<W: io::Write, const CONFIG: EncodedConfig>( + output: &mut W, + offset: UtcOffset, +) -> Result<usize, error::Format> { + if Iso8601::<CONFIG>::FORMAT_TIME && offset.is_utc() { + return Ok(write(output, b"Z")?); + } + + let mut bytes = 0; + + let (hours, minutes, seconds) = offset.as_hms(); + if seconds != 0 { + return Err(error::Format::InvalidComponent("offset_second")); + } + bytes += write_if_else(output, offset.is_negative(), b"-", b"+")?; + bytes += format_number_pad_zero::<2, _, _>(output, hours.unsigned_abs())?; + + if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Hour && minutes != 0 { + return Err(error::Format::InvalidComponent("offset_minute")); + } else if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Minute { + bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?; + bytes += format_number_pad_zero::<2, _, _>(output, minutes.unsigned_abs())?; + } + + Ok(bytes) +} diff --git a/third_party/rust/time/src/formatting/mod.rs b/third_party/rust/time/src/formatting/mod.rs new file mode 100644 index 0000000000..e5409cc58f --- /dev/null +++ b/third_party/rust/time/src/formatting/mod.rs @@ -0,0 +1,506 @@ +//! Formatting for various types. + +pub(crate) mod formattable; +mod iso8601; + +use core::num::NonZeroU8; +use std::io; + +pub use self::formattable::Formattable; +use crate::format_description::{modifier, Component}; +use crate::{error, Date, Time, UtcOffset}; + +#[allow(clippy::missing_docs_in_private_items)] +const MONTH_NAMES: [&[u8]; 12] = [ + b"January", + b"February", + b"March", + b"April", + b"May", + b"June", + b"July", + b"August", + b"September", + b"October", + b"November", + b"December", +]; + +#[allow(clippy::missing_docs_in_private_items)] +const WEEKDAY_NAMES: [&[u8]; 7] = [ + b"Monday", + b"Tuesday", + b"Wednesday", + b"Thursday", + b"Friday", + b"Saturday", + b"Sunday", +]; + +// region: extension trait +/// A trait that indicates the formatted width of the value can be determined. +/// +/// Note that this should not be implemented for any signed integers. This forces the caller to +/// write the sign if desired. +pub(crate) trait DigitCount { + /// The number of digits in the stringified value. + fn num_digits(self) -> u8; +} +impl DigitCount for u8 { + fn num_digits(self) -> u8 { + // Using a lookup table as with u32 is *not* faster in standalone benchmarks. + if self < 10 { + 1 + } else if self < 100 { + 2 + } else { + 3 + } + } +} +impl DigitCount for u16 { + fn num_digits(self) -> u8 { + // Using a lookup table as with u32 is *not* faster in standalone benchmarks. + if self < 10 { + 1 + } else if self < 100 { + 2 + } else if self < 1_000 { + 3 + } else if self < 10_000 { + 4 + } else { + 5 + } + } +} + +impl DigitCount for u32 { + fn num_digits(self) -> u8 { + /// Lookup table + const TABLE: &[u64] = &[ + 0x0001_0000_0000, + 0x0001_0000_0000, + 0x0001_0000_0000, + 0x0001_FFFF_FFF6, + 0x0002_0000_0000, + 0x0002_0000_0000, + 0x0002_FFFF_FF9C, + 0x0003_0000_0000, + 0x0003_0000_0000, + 0x0003_FFFF_FC18, + 0x0004_0000_0000, + 0x0004_0000_0000, + 0x0004_0000_0000, + 0x0004_FFFF_D8F0, + 0x0005_0000_0000, + 0x0005_0000_0000, + 0x0005_FFFE_7960, + 0x0006_0000_0000, + 0x0006_0000_0000, + 0x0006_FFF0_BDC0, + 0x0007_0000_0000, + 0x0007_0000_0000, + 0x0007_0000_0000, + 0x0007_FF67_6980, + 0x0008_0000_0000, + 0x0008_0000_0000, + 0x0008_FA0A_1F00, + 0x0009_0000_0000, + 0x0009_0000_0000, + 0x0009_C465_3600, + 0x000A_0000_0000, + 0x000A_0000_0000, + ]; + ((self as u64 + TABLE[31_u32.saturating_sub(self.leading_zeros()) as usize]) >> 32) as _ + } +} +// endregion extension trait + +/// Write all bytes to the output, returning the number of bytes written. +pub(crate) fn write(output: &mut impl io::Write, bytes: &[u8]) -> io::Result<usize> { + output.write_all(bytes)?; + Ok(bytes.len()) +} + +/// If `pred` is true, write all bytes to the output, returning the number of bytes written. +pub(crate) fn write_if(output: &mut impl io::Write, pred: bool, bytes: &[u8]) -> io::Result<usize> { + if pred { write(output, bytes) } else { Ok(0) } +} + +/// If `pred` is true, write `true_bytes` to the output. Otherwise, write `false_bytes`. +pub(crate) fn write_if_else( + output: &mut impl io::Write, + pred: bool, + true_bytes: &[u8], + false_bytes: &[u8], +) -> io::Result<usize> { + write(output, if pred { true_bytes } else { false_bytes }) +} + +/// Write the floating point number to the output, returning the number of bytes written. +/// +/// This method accepts the number of digits before and after the decimal. The value will be padded +/// with zeroes to the left if necessary. +pub(crate) fn format_float( + output: &mut impl io::Write, + value: f64, + digits_before_decimal: u8, + digits_after_decimal: Option<NonZeroU8>, +) -> io::Result<usize> { + match digits_after_decimal { + Some(digits_after_decimal) => { + let digits_after_decimal = digits_after_decimal.get() as usize; + let width = digits_before_decimal as usize + 1 + digits_after_decimal; + write!( + output, + "{value:0>width$.digits_after_decimal$}", + value = value, + width = width, + digits_after_decimal = digits_after_decimal, + )?; + Ok(width) + } + None => { + let value = value.trunc() as u64; + let width = digits_before_decimal as usize; + write!(output, "{value:0>width$?}", value = value, width = width)?; + Ok(width) + } + } +} + +/// Format a number with the provided padding and width. +/// +/// The sign must be written by the caller. +pub(crate) fn format_number<const WIDTH: u8, W: io::Write, V: itoa::Integer + DigitCount + Copy>( + output: &mut W, + value: V, + padding: modifier::Padding, +) -> Result<usize, io::Error> { + match padding { + modifier::Padding::Space => format_number_pad_space::<WIDTH, _, _>(output, value), + modifier::Padding::Zero => format_number_pad_zero::<WIDTH, _, _>(output, value), + modifier::Padding::None => write(output, itoa::Buffer::new().format(value).as_bytes()), + } +} + +/// Format a number with the provided width and spaces as padding. +/// +/// The sign must be written by the caller. +pub(crate) fn format_number_pad_space< + const WIDTH: u8, + W: io::Write, + V: itoa::Integer + DigitCount + Copy, +>( + output: &mut W, + value: V, +) -> Result<usize, io::Error> { + let mut bytes = 0; + for _ in 0..(WIDTH.saturating_sub(value.num_digits())) { + bytes += write(output, b" ")?; + } + bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?; + Ok(bytes) +} + +/// Format a number with the provided width and zeros as padding. +/// +/// The sign must be written by the caller. +pub(crate) fn format_number_pad_zero< + const WIDTH: u8, + W: io::Write, + V: itoa::Integer + DigitCount + Copy, +>( + output: &mut W, + value: V, +) -> Result<usize, io::Error> { + let mut bytes = 0; + for _ in 0..(WIDTH.saturating_sub(value.num_digits())) { + bytes += write(output, b"0")?; + } + bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?; + Ok(bytes) +} + +/// Format the provided component into the designated output. An `Err` will be returned if the +/// component requires information that it does not provide or if the value cannot be output to the +/// stream. +pub(crate) fn format_component( + output: &mut impl io::Write, + component: Component, + date: Option<Date>, + time: Option<Time>, + offset: Option<UtcOffset>, +) -> Result<usize, error::Format> { + use Component::*; + Ok(match (component, date, time, offset) { + (Day(modifier), Some(date), ..) => fmt_day(output, date, modifier)?, + (Month(modifier), Some(date), ..) => fmt_month(output, date, modifier)?, + (Ordinal(modifier), Some(date), ..) => fmt_ordinal(output, date, modifier)?, + (Weekday(modifier), Some(date), ..) => fmt_weekday(output, date, modifier)?, + (WeekNumber(modifier), Some(date), ..) => fmt_week_number(output, date, modifier)?, + (Year(modifier), Some(date), ..) => fmt_year(output, date, modifier)?, + (Hour(modifier), _, Some(time), _) => fmt_hour(output, time, modifier)?, + (Minute(modifier), _, Some(time), _) => fmt_minute(output, time, modifier)?, + (Period(modifier), _, Some(time), _) => fmt_period(output, time, modifier)?, + (Second(modifier), _, Some(time), _) => fmt_second(output, time, modifier)?, + (Subsecond(modifier), _, Some(time), _) => fmt_subsecond(output, time, modifier)?, + (OffsetHour(modifier), .., Some(offset)) => fmt_offset_hour(output, offset, modifier)?, + (OffsetMinute(modifier), .., Some(offset)) => fmt_offset_minute(output, offset, modifier)?, + (OffsetSecond(modifier), .., Some(offset)) => fmt_offset_second(output, offset, modifier)?, + _ => return Err(error::Format::InsufficientTypeInformation), + }) +} + +// region: date formatters +/// Format the day into the designated output. +fn fmt_day( + output: &mut impl io::Write, + date: Date, + modifier::Day { padding }: modifier::Day, +) -> Result<usize, io::Error> { + format_number::<2, _, _>(output, date.day(), padding) +} + +/// Format the month into the designated output. +fn fmt_month( + output: &mut impl io::Write, + date: Date, + modifier::Month { + padding, + repr, + case_sensitive: _, // no effect on formatting + }: modifier::Month, +) -> Result<usize, io::Error> { + match repr { + modifier::MonthRepr::Numerical => { + format_number::<2, _, _>(output, date.month() as u8, padding) + } + modifier::MonthRepr::Long => write(output, MONTH_NAMES[date.month() as usize - 1]), + modifier::MonthRepr::Short => write(output, &MONTH_NAMES[date.month() as usize - 1][..3]), + } +} + +/// Format the ordinal into the designated output. +fn fmt_ordinal( + output: &mut impl io::Write, + date: Date, + modifier::Ordinal { padding }: modifier::Ordinal, +) -> Result<usize, io::Error> { + format_number::<3, _, _>(output, date.ordinal(), padding) +} + +/// Format the weekday into the designated output. +fn fmt_weekday( + output: &mut impl io::Write, + date: Date, + modifier::Weekday { + repr, + one_indexed, + case_sensitive: _, // no effect on formatting + }: modifier::Weekday, +) -> Result<usize, io::Error> { + match repr { + modifier::WeekdayRepr::Short => write( + output, + &WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3], + ), + modifier::WeekdayRepr::Long => write( + output, + WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize], + ), + modifier::WeekdayRepr::Sunday => format_number::<1, _, _>( + output, + date.weekday().number_days_from_sunday() + one_indexed as u8, + modifier::Padding::None, + ), + modifier::WeekdayRepr::Monday => format_number::<1, _, _>( + output, + date.weekday().number_days_from_monday() + one_indexed as u8, + modifier::Padding::None, + ), + } +} + +/// Format the week number into the designated output. +fn fmt_week_number( + output: &mut impl io::Write, + date: Date, + modifier::WeekNumber { padding, repr }: modifier::WeekNumber, +) -> Result<usize, io::Error> { + format_number::<2, _, _>( + output, + match repr { + modifier::WeekNumberRepr::Iso => date.iso_week(), + modifier::WeekNumberRepr::Sunday => date.sunday_based_week(), + modifier::WeekNumberRepr::Monday => date.monday_based_week(), + }, + padding, + ) +} + +/// Format the year into the designated output. +fn fmt_year( + output: &mut impl io::Write, + date: Date, + modifier::Year { + padding, + repr, + iso_week_based, + sign_is_mandatory, + }: modifier::Year, +) -> Result<usize, io::Error> { + let full_year = if iso_week_based { + date.iso_year_week().0 + } else { + date.year() + }; + let value = match repr { + modifier::YearRepr::Full => full_year, + modifier::YearRepr::LastTwo => (full_year % 100).abs(), + }; + let format_number = match repr { + #[cfg(feature = "large-dates")] + modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6, _, _>, + #[cfg(feature = "large-dates")] + modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5, _, _>, + modifier::YearRepr::Full => format_number::<4, _, _>, + modifier::YearRepr::LastTwo => format_number::<2, _, _>, + }; + let mut bytes = 0; + if repr != modifier::YearRepr::LastTwo { + if full_year < 0 { + bytes += write(output, b"-")?; + } else if sign_is_mandatory || cfg!(feature = "large-dates") && full_year >= 10_000 { + bytes += write(output, b"+")?; + } + } + bytes += format_number(output, value.unsigned_abs(), padding)?; + Ok(bytes) +} +// endregion date formatters + +// region: time formatters +/// Format the hour into the designated output. +fn fmt_hour( + output: &mut impl io::Write, + time: Time, + modifier::Hour { + padding, + is_12_hour_clock, + }: modifier::Hour, +) -> Result<usize, io::Error> { + let value = match (time.hour(), is_12_hour_clock) { + (hour, false) => hour, + (0 | 12, true) => 12, + (hour, true) if hour < 12 => hour, + (hour, true) => hour - 12, + }; + format_number::<2, _, _>(output, value, padding) +} + +/// Format the minute into the designated output. +fn fmt_minute( + output: &mut impl io::Write, + time: Time, + modifier::Minute { padding }: modifier::Minute, +) -> Result<usize, io::Error> { + format_number::<2, _, _>(output, time.minute(), padding) +} + +/// Format the period into the designated output. +fn fmt_period( + output: &mut impl io::Write, + time: Time, + modifier::Period { + is_uppercase, + case_sensitive: _, // no effect on formatting + }: modifier::Period, +) -> Result<usize, io::Error> { + match (time.hour() >= 12, is_uppercase) { + (false, false) => write(output, b"am"), + (false, true) => write(output, b"AM"), + (true, false) => write(output, b"pm"), + (true, true) => write(output, b"PM"), + } +} + +/// Format the second into the designated output. +fn fmt_second( + output: &mut impl io::Write, + time: Time, + modifier::Second { padding }: modifier::Second, +) -> Result<usize, io::Error> { + format_number::<2, _, _>(output, time.second(), padding) +} + +/// Format the subsecond into the designated output. +fn fmt_subsecond<W: io::Write>( + output: &mut W, + time: Time, + modifier::Subsecond { digits }: modifier::Subsecond, +) -> Result<usize, io::Error> { + use modifier::SubsecondDigits::*; + let nanos = time.nanosecond(); + + if digits == Nine || (digits == OneOrMore && nanos % 10 != 0) { + format_number_pad_zero::<9, _, _>(output, nanos) + } else if digits == Eight || (digits == OneOrMore && (nanos / 10) % 10 != 0) { + format_number_pad_zero::<8, _, _>(output, nanos / 10) + } else if digits == Seven || (digits == OneOrMore && (nanos / 100) % 10 != 0) { + format_number_pad_zero::<7, _, _>(output, nanos / 100) + } else if digits == Six || (digits == OneOrMore && (nanos / 1_000) % 10 != 0) { + format_number_pad_zero::<6, _, _>(output, nanos / 1_000) + } else if digits == Five || (digits == OneOrMore && (nanos / 10_000) % 10 != 0) { + format_number_pad_zero::<5, _, _>(output, nanos / 10_000) + } else if digits == Four || (digits == OneOrMore && (nanos / 100_000) % 10 != 0) { + format_number_pad_zero::<4, _, _>(output, nanos / 100_000) + } else if digits == Three || (digits == OneOrMore && (nanos / 1_000_000) % 10 != 0) { + format_number_pad_zero::<3, _, _>(output, nanos / 1_000_000) + } else if digits == Two || (digits == OneOrMore && (nanos / 10_000_000) % 10 != 0) { + format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000) + } else { + format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000) + } +} +// endregion time formatters + +// region: offset formatters +/// Format the offset hour into the designated output. +fn fmt_offset_hour( + output: &mut impl io::Write, + offset: UtcOffset, + modifier::OffsetHour { + padding, + sign_is_mandatory, + }: modifier::OffsetHour, +) -> Result<usize, io::Error> { + let mut bytes = 0; + if offset.is_negative() { + bytes += write(output, b"-")?; + } else if sign_is_mandatory { + bytes += write(output, b"+")?; + } + bytes += format_number::<2, _, _>(output, offset.whole_hours().unsigned_abs(), padding)?; + Ok(bytes) +} + +/// Format the offset minute into the designated output. +fn fmt_offset_minute( + output: &mut impl io::Write, + offset: UtcOffset, + modifier::OffsetMinute { padding }: modifier::OffsetMinute, +) -> Result<usize, io::Error> { + format_number::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs(), padding) +} + +/// Format the offset second into the designated output. +fn fmt_offset_second( + output: &mut impl io::Write, + offset: UtcOffset, + modifier::OffsetSecond { padding }: modifier::OffsetSecond, +) -> Result<usize, io::Error> { + format_number::<2, _, _>(output, offset.seconds_past_minute().unsigned_abs(), padding) +} +// endregion offset formatters diff --git a/third_party/rust/time/src/instant.rs b/third_party/rust/time/src/instant.rs new file mode 100644 index 0000000000..2d2f65eefb --- /dev/null +++ b/third_party/rust/time/src/instant.rs @@ -0,0 +1,262 @@ +//! The [`Instant`] struct and its associated `impl`s. + +use core::borrow::Borrow; +use core::cmp::{Ord, Ordering, PartialEq, PartialOrd}; +use core::ops::{Add, Sub}; +use core::time::Duration as StdDuration; +use std::time::Instant as StdInstant; + +use crate::Duration; + +/// A measurement of a monotonically non-decreasing clock. Opaque and useful only with [`Duration`]. +/// +/// Instants are always guaranteed to be no less than any previously measured instant when created, +/// and are often useful for tasks such as measuring benchmarks or timing how long an operation +/// takes. +/// +/// Note, however, that instants are not guaranteed to be **steady**. In other words, each tick of +/// the underlying clock may not be the same length (e.g. some seconds may be longer than others). +/// An instant may jump forwards or experience time dilation (slow down or speed up), but it will +/// never go backwards. +/// +/// Instants are opaque types that can only be compared to one another. There is no method to get +/// "the number of seconds" from an instant. Instead, it only allows measuring the duration between +/// two instants (or comparing two instants). +/// +/// This implementation allows for operations with signed [`Duration`]s, but is otherwise identical +/// to [`std::time::Instant`]. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Instant(pub StdInstant); + +impl Instant { + // region: delegation + /// Returns an `Instant` corresponding to "now". + /// + /// ```rust + /// # use time::Instant; + /// println!("{:?}", Instant::now()); + /// ``` + pub fn now() -> Self { + Self(StdInstant::now()) + } + + /// Returns the amount of time elapsed since this instant was created. The duration will always + /// be nonnegative if the instant is not synthetically created. + /// + /// ```rust + /// # use time::{Instant, ext::{NumericalStdDuration, NumericalDuration}}; + /// # use std::thread; + /// let instant = Instant::now(); + /// thread::sleep(1.std_milliseconds()); + /// assert!(instant.elapsed() >= 1.milliseconds()); + /// ``` + pub fn elapsed(self) -> Duration { + Self::now() - self + } + // endregion delegation + + // region: checked arithmetic + /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as + /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` + /// otherwise. + /// + /// ```rust + /// # use time::{Instant, ext::NumericalDuration}; + /// let now = Instant::now(); + /// assert_eq!(now.checked_add(5.seconds()), Some(now + 5.seconds())); + /// assert_eq!(now.checked_add((-5).seconds()), Some(now + (-5).seconds())); + /// ``` + pub fn checked_add(self, duration: Duration) -> Option<Self> { + if duration.is_zero() { + Some(self) + } else if duration.is_positive() { + self.0.checked_add(duration.unsigned_abs()).map(Self) + } else { + debug_assert!(duration.is_negative()); + self.0.checked_sub(duration.unsigned_abs()).map(Self) + } + } + + /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as + /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` + /// otherwise. + /// + /// ```rust + /// # use time::{Instant, ext::NumericalDuration}; + /// let now = Instant::now(); + /// assert_eq!(now.checked_sub(5.seconds()), Some(now - 5.seconds())); + /// assert_eq!(now.checked_sub((-5).seconds()), Some(now - (-5).seconds())); + /// ``` + pub fn checked_sub(self, duration: Duration) -> Option<Self> { + if duration.is_zero() { + Some(self) + } else if duration.is_positive() { + self.0.checked_sub(duration.unsigned_abs()).map(Self) + } else { + debug_assert!(duration.is_negative()); + self.0.checked_add(duration.unsigned_abs()).map(Self) + } + } + // endregion checked arithmetic + + /// Obtain the inner [`std::time::Instant`]. + /// + /// ```rust + /// # use time::Instant; + /// let now = Instant::now(); + /// assert_eq!(now.into_inner(), now.0); + /// ``` + pub const fn into_inner(self) -> StdInstant { + self.0 + } +} + +// region: trait impls +impl From<StdInstant> for Instant { + fn from(instant: StdInstant) -> Self { + Self(instant) + } +} + +impl From<Instant> for StdInstant { + fn from(instant: Instant) -> Self { + instant.0 + } +} + +impl Sub for Instant { + type Output = Duration; + + fn sub(self, other: Self) -> Self::Output { + match self.0.cmp(&other.0) { + Ordering::Equal => Duration::ZERO, + Ordering::Greater => (self.0 - other.0) + .try_into() + .expect("overflow converting `std::time::Duration` to `time::Duration`"), + Ordering::Less => -Duration::try_from(other.0 - self.0) + .expect("overflow converting `std::time::Duration` to `time::Duration`"), + } + } +} + +impl Sub<StdInstant> for Instant { + type Output = Duration; + + fn sub(self, other: StdInstant) -> Self::Output { + self - Self(other) + } +} + +impl Sub<Instant> for StdInstant { + type Output = Duration; + + fn sub(self, other: Instant) -> Self::Output { + Instant(self) - other + } +} + +impl Add<Duration> for Instant { + type Output = Self; + + fn add(self, duration: Duration) -> Self::Output { + if duration.is_positive() { + Self(self.0 + duration.unsigned_abs()) + } else if duration.is_negative() { + Self(self.0 - duration.unsigned_abs()) + } else { + debug_assert!(duration.is_zero()); + self + } + } +} + +impl Add<Duration> for StdInstant { + type Output = Self; + + fn add(self, duration: Duration) -> Self::Output { + (Instant(self) + duration).0 + } +} + +impl Add<StdDuration> for Instant { + type Output = Self; + + fn add(self, duration: StdDuration) -> Self::Output { + Self(self.0 + duration) + } +} + +impl_add_assign!(Instant: Duration, StdDuration); +impl_add_assign!(StdInstant: Duration); + +impl Sub<Duration> for Instant { + type Output = Self; + + fn sub(self, duration: Duration) -> Self::Output { + if duration.is_positive() { + Self(self.0 - duration.unsigned_abs()) + } else if duration.is_negative() { + Self(self.0 + duration.unsigned_abs()) + } else { + debug_assert!(duration.is_zero()); + self + } + } +} + +impl Sub<Duration> for StdInstant { + type Output = Self; + + fn sub(self, duration: Duration) -> Self::Output { + (Instant(self) - duration).0 + } +} + +impl Sub<StdDuration> for Instant { + type Output = Self; + + fn sub(self, duration: StdDuration) -> Self::Output { + Self(self.0 - duration) + } +} + +impl_sub_assign!(Instant: Duration, StdDuration); +impl_sub_assign!(StdInstant: Duration); + +impl PartialEq<StdInstant> for Instant { + fn eq(&self, rhs: &StdInstant) -> bool { + self.0.eq(rhs) + } +} + +impl PartialEq<Instant> for StdInstant { + fn eq(&self, rhs: &Instant) -> bool { + self.eq(&rhs.0) + } +} + +impl PartialOrd<StdInstant> for Instant { + fn partial_cmp(&self, rhs: &StdInstant) -> Option<Ordering> { + self.0.partial_cmp(rhs) + } +} + +impl PartialOrd<Instant> for StdInstant { + fn partial_cmp(&self, rhs: &Instant) -> Option<Ordering> { + self.partial_cmp(&rhs.0) + } +} + +impl AsRef<StdInstant> for Instant { + fn as_ref(&self) -> &StdInstant { + &self.0 + } +} + +impl Borrow<StdInstant> for Instant { + fn borrow(&self) -> &StdInstant { + &self.0 + } +} +// endregion trait impls diff --git a/third_party/rust/time/src/lib.rs b/third_party/rust/time/src/lib.rs new file mode 100644 index 0000000000..b9868c1788 --- /dev/null +++ b/third_party/rust/time/src/lib.rs @@ -0,0 +1,357 @@ +//! # Feature flags +//! +//! This crate exposes a number of features. These can be enabled or disabled as shown +//! [in Cargo's documentation](https://doc.rust-lang.org/cargo/reference/features.html). Features +//! are _disabled_ by default unless otherwise noted. +//! +//! Reliance on a given feature is always indicated alongside the item definition. +//! +//! - `std` (_enabled by default, implicitly enables `alloc`_) +//! +//! This enables a number of features that depend on the standard library. +//! +//! - `alloc` (_enabled by default via `std`_) +//! +//! Enables a number of features that require the ability to dynamically allocate memory. +//! +//! - `macros` +//! +//! Enables macros that provide compile-time verification of values and intuitive syntax. +//! +//! - `formatting` (_implicitly enables `std`_) +//! +//! Enables formatting of most structs. +//! +//! - `parsing` +//! +//! Enables parsing of most structs. +//! +//! - `local-offset` (_implicitly enables `std`_) +//! +//! This feature enables a number of methods that allow obtaining the system's UTC offset. +//! +//! - `large-dates` +//! +//! By default, only years within the ±9999 range (inclusive) are supported. If you need support +//! for years outside this range, consider enabling this feature; the supported range will be +//! increased to ±999,999. +//! +//! Note that enabling this feature has some costs, as it means forgoing some optimizations. +//! Ambiguities may be introduced when parsing that would not otherwise exist. +//! +//! - `serde` +//! +//! Enables [serde](https://docs.rs/serde) support for all types except [`Instant`]. +//! +//! - `serde-human-readable` (_implicitly enables `serde`, `formatting`, and `parsing`_) +//! +//! Allows serde representations to use a human-readable format. This is determined by the +//! serializer, not the user. If this feature is not enabled or if the serializer requests a +//! non-human-readable format, a format optimized for binary representation will be used. +//! +//! Libraries should never enable this feature, as the decision of what format to use should be up +//! to the user. +//! +//! - `serde-well-known` (_implicitly enables `serde-human-readable`_) +//! +//! _This feature flag is deprecated and will be removed in a future breaking release. Use the +//! `serde-human-readable` feature instead._ +//! +//! Enables support for serializing and deserializing well-known formats using serde's +//! [`#[with]` attribute](https://serde.rs/field-attrs.html#with). +//! +//! - `rand` +//! +//! Enables [rand](https://docs.rs/rand) support for all types. +//! +//! - `quickcheck` (_implicitly enables `alloc`_) +//! +//! Enables [quickcheck](https://docs.rs/quickcheck) support for all types except [`Instant`]. +//! +//! - `wasm-bindgen` +//! +//! Enables [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) support for converting +//! [JavaScript dates](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Date.html), as +//! well as obtaining the UTC offset from JavaScript. +//! +//! <small> +//! One feature only available to end users is the <code>unsound_local_offset</code> cfg. This +//! enables obtaining the system's UTC offset even when it is unsound. To enable this, use the +//! <code>RUSTFLAGS</code> environment variable. This is untested and officially unsupported. Do not +//! use this unless you understand the risk. +//! </small> + +#![doc(html_playground_url = "https://play.rust-lang.org")] +#![cfg_attr(__time_03_docs, feature(doc_auto_cfg, doc_notable_trait))] +#![cfg_attr(not(feature = "std"), no_std)] +#![deny( + anonymous_parameters, + clippy::all, + clippy::alloc_instead_of_core, + clippy::explicit_auto_deref, + clippy::obfuscated_if_else, + clippy::std_instead_of_core, + clippy::undocumented_unsafe_blocks, + const_err, + illegal_floating_point_literal_pattern, + late_bound_lifetime_arguments, + path_statements, + patterns_in_fns_without_body, + rust_2018_idioms, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_extern_crates, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links +)] +#![warn( + clippy::dbg_macro, + clippy::decimal_literal_representation, + clippy::get_unwrap, + clippy::missing_docs_in_private_items, + clippy::nursery, + clippy::print_stdout, + clippy::todo, + clippy::unimplemented, + clippy::unnested_or_patterns, + clippy::unwrap_in_result, + clippy::unwrap_used, + clippy::use_debug, + deprecated_in_future, + missing_copy_implementations, + missing_debug_implementations, + unused_qualifications, + variant_size_differences +)] +#![allow( + clippy::redundant_pub_crate, // suggests bad style + clippy::option_if_let_else, // suggests terrible code + clippy::unused_peekable, // temporary due to bug: remove when Rust 1.66 is released + clippy::std_instead_of_core, // temporary due to bug: remove when Rust 1.66 is released +)] +#![doc(html_favicon_url = "https://avatars0.githubusercontent.com/u/55999857")] +#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55999857")] +#![doc(test(attr(deny(warnings))))] + +#[allow(unused_extern_crates)] +#[cfg(feature = "alloc")] +extern crate alloc; + +// region: macros +/// Helper macro for easily implementing `OpAssign`. +macro_rules! __impl_assign { + ($sym:tt $op:ident $fn:ident $target:ty : $($(#[$attr:meta])* $t:ty),+) => {$( + #[allow(unused_qualifications)] + $(#[$attr])* + impl core::ops::$op<$t> for $target { + fn $fn(&mut self, rhs: $t) { + *self = *self $sym rhs; + } + } + )+}; +} + +/// Implement `AddAssign` for the provided types. +macro_rules! impl_add_assign { + ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => { + __impl_assign!(+ AddAssign add_assign $target : $($(#[$attr])* $t),+); + }; +} + +/// Implement `SubAssign` for the provided types. +macro_rules! impl_sub_assign { + ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => { + __impl_assign!(- SubAssign sub_assign $target : $($(#[$attr])* $t),+); + }; +} + +/// Implement `MulAssign` for the provided types. +macro_rules! impl_mul_assign { + ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => { + __impl_assign!(* MulAssign mul_assign $target : $($(#[$attr])* $t),+); + }; +} + +/// Implement `DivAssign` for the provided types. +macro_rules! impl_div_assign { + ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => { + __impl_assign!(/ DivAssign div_assign $target : $($(#[$attr])* $t),+); + }; +} + +/// Division of integers, rounding the resulting value towards negative infinity. +macro_rules! div_floor { + ($a:expr, $b:expr) => {{ + let _a = $a; + let _b = $b; + + let (_quotient, _remainder) = (_a / _b, _a % _b); + + if (_remainder > 0 && _b < 0) || (_remainder < 0 && _b > 0) { + _quotient - 1 + } else { + _quotient + } + }}; +} + +/// Cascade an out-of-bounds value. +macro_rules! cascade { + (@ordinal ordinal) => {}; + (@year year) => {}; + + // Cascade an out-of-bounds value from "from" to "to". + ($from:ident in $min:literal.. $max:literal => $to:tt) => { + #[allow(unused_comparisons, unused_assignments)] + if $from >= $max { + $from -= $max - $min; + $to += 1; + } else if $from < $min { + $from += $max - $min; + $to -= 1; + } + }; + + // Special case the ordinal-to-year cascade, as it has different behavior. + ($ordinal:ident => $year:ident) => { + // We need to actually capture the idents. Without this, macro hygiene causes errors. + cascade!(@ordinal $ordinal); + cascade!(@year $year); + #[allow(unused_assignments)] + if $ordinal > crate::util::days_in_year($year) as i16 { + $ordinal -= crate::util::days_in_year($year) as i16; + $year += 1; + } else if $ordinal < 1 { + $year -= 1; + $ordinal += crate::util::days_in_year($year) as i16; + } + }; +} + +/// Returns `Err(error::ComponentRange)` if the value is not in range. +macro_rules! ensure_value_in_range { + ($value:ident in $start:expr => $end:expr) => {{ + let _start = $start; + let _end = $end; + #[allow(trivial_numeric_casts, unused_comparisons)] + if $value < _start || $value > _end { + return Err(crate::error::ComponentRange { + name: stringify!($value), + minimum: _start as _, + maximum: _end as _, + value: $value as _, + conditional_range: false, + }); + } + }}; + + ($value:ident conditionally in $start:expr => $end:expr) => {{ + let _start = $start; + let _end = $end; + #[allow(trivial_numeric_casts, unused_comparisons)] + if $value < _start || $value > _end { + return Err(crate::error::ComponentRange { + name: stringify!($value), + minimum: _start as _, + maximum: _end as _, + value: $value as _, + conditional_range: true, + }); + } + }}; +} + +/// Try to unwrap an expression, returning if not possible. +/// +/// This is similar to the `?` operator, but does not perform `.into()`. Because of this, it is +/// usable in `const` contexts. +macro_rules! const_try { + ($e:expr) => { + match $e { + Ok(value) => value, + Err(error) => return Err(error), + } + }; +} + +/// Try to unwrap an expression, returning if not possible. +/// +/// This is similar to the `?` operator, but is usable in `const` contexts. +macro_rules! const_try_opt { + ($e:expr) => { + match $e { + Some(value) => value, + None => return None, + } + }; +} + +/// Try to unwrap an expression, panicking if not possible. +/// +/// This is similar to `$e.expect($message)`, but is usable in `const` contexts. +macro_rules! expect_opt { + ($e:expr, $message:literal) => { + match $e { + Some(value) => value, + None => crate::expect_failed($message), + } + }; +} +// endregion macros + +mod date; +mod duration; +pub mod error; +pub mod ext; +#[cfg(any(feature = "formatting", feature = "parsing"))] +pub mod format_description; +#[cfg(feature = "formatting")] +pub mod formatting; +#[cfg(feature = "std")] +mod instant; +#[cfg(feature = "macros")] +pub mod macros; +mod month; +mod offset_date_time; +#[cfg(feature = "parsing")] +pub mod parsing; +mod primitive_date_time; +#[cfg(feature = "quickcheck")] +mod quickcheck; +#[cfg(feature = "rand")] +mod rand; +#[cfg(feature = "serde")] +#[allow(missing_copy_implementations, missing_debug_implementations)] +pub mod serde; +mod sys; +#[cfg(test)] +mod tests; +mod time; +mod utc_offset; +pub mod util; +mod weekday; + +pub use crate::date::Date; +pub use crate::duration::Duration; +pub use crate::error::Error; +#[cfg(feature = "std")] +pub use crate::instant::Instant; +pub use crate::month::Month; +pub use crate::offset_date_time::OffsetDateTime; +pub use crate::primitive_date_time::PrimitiveDateTime; +pub use crate::time::Time; +pub use crate::utc_offset::UtcOffset; +pub use crate::weekday::Weekday; + +/// An alias for [`std::result::Result`] with a generic error from the time crate. +pub type Result<T> = core::result::Result<T, Error>; + +/// This is a separate function to reduce the code size of `expect_opt!`. +#[inline(never)] +#[cold] +#[track_caller] +const fn expect_failed(message: &str) -> ! { + panic!("{}", message) +} diff --git a/third_party/rust/time/src/macros.rs b/third_party/rust/time/src/macros.rs new file mode 100644 index 0000000000..4f295e2ed8 --- /dev/null +++ b/third_party/rust/time/src/macros.rs @@ -0,0 +1,132 @@ +//! Macros to construct statically known values. + +/// Construct a [`Date`](crate::Date) with a statically known value. +/// +/// The resulting expression can be used in `const` or `static` declarations. +/// +/// Three formats are supported: year-week-weekday, year-ordinal, and year-month-day. +/// +/// ```rust +/// # use time::{Date, Weekday::*, Month, macros::date}; +/// assert_eq!( +/// date!(2020 - W 01 - 3), +/// Date::from_iso_week_date(2020, 1, Wednesday)? +/// ); +/// assert_eq!(date!(2020 - 001), Date::from_ordinal_date(2020, 1)?); +/// assert_eq!( +/// date!(2020 - 01 - 01), +/// Date::from_calendar_date(2020, Month::January, 1)? +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +pub use time_macros::date; +/// Construct a [`PrimitiveDateTime`] or [`OffsetDateTime`] with a statically known value. +/// +/// The resulting expression can be used in `const` or `static` declarations. +/// +/// The syntax accepted by this macro is the same as [`date!`] and [`time!`], with an optional +/// [`offset!`], all space-separated. If an [`offset!`] is provided, the resulting value will +/// be an [`OffsetDateTime`]; otherwise it will be a [`PrimitiveDateTime`]. +/// +/// [`OffsetDateTime`]: crate::OffsetDateTime +/// [`PrimitiveDateTime`]: crate::PrimitiveDateTime +/// +/// ```rust +/// # use time::{Date, Month, macros::datetime, UtcOffset}; +/// assert_eq!( +/// datetime!(2020-01-01 0:00), +/// Date::from_calendar_date(2020, Month::January, 1)?.midnight() +/// ); +/// assert_eq!( +/// datetime!(2020-01-01 0:00 UTC), +/// Date::from_calendar_date(2020, Month::January, 1)?.midnight().assume_utc() +/// ); +/// assert_eq!( +/// datetime!(2020-01-01 0:00 -1), +/// Date::from_calendar_date(2020, Month::January, 1)?.midnight() +/// .assume_offset(UtcOffset::from_hms(-1, 0, 0)?) +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +pub use time_macros::datetime; +/// Equivalent of performing [`format_description::parse()`] at compile time. +/// +/// Using the macro instead of the function results in a static slice rather than a [`Vec`], +/// such that it can be used in `#![no_alloc]` situations. +/// +/// The resulting expression can be used in `const` or `static` declarations, and implements +/// the sealed traits required for both formatting and parsing. +#[cfg_attr(feature = "alloc", doc = "```rust")] +#[cfg_attr(not(feature = "alloc"), doc = "```rust,ignore")] +/// # use time::{format_description, macros::format_description}; +/// assert_eq!( +/// format_description!("[hour]:[minute]:[second]"), +/// format_description::parse("[hour]:[minute]:[second]")? +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +/// +/// The syntax accepted by this macro is the same as [`format_description::parse()`], which can +/// be found in [the book](https://time-rs.github.io/book/api/format-description.html). +/// +/// [`format_description::parse()`]: crate::format_description::parse() +#[cfg(any(feature = "formatting", feature = "parsing"))] +pub use time_macros::format_description; +/// Construct a [`UtcOffset`](crate::UtcOffset) with a statically known value. +/// +/// The resulting expression can be used in `const` or `static` declarations. +/// +/// A sign and the hour must be provided; minutes and seconds default to zero. `UTC` (both +/// uppercase and lowercase) is also allowed. +/// +/// ```rust +/// # use time::{UtcOffset, macros::offset}; +/// assert_eq!(offset!(UTC), UtcOffset::from_hms(0, 0, 0)?); +/// assert_eq!(offset!(utc), UtcOffset::from_hms(0, 0, 0)?); +/// assert_eq!(offset!(+0), UtcOffset::from_hms(0, 0, 0)?); +/// assert_eq!(offset!(+1), UtcOffset::from_hms(1, 0, 0)?); +/// assert_eq!(offset!(-1), UtcOffset::from_hms(-1, 0, 0)?); +/// assert_eq!(offset!(+1:30), UtcOffset::from_hms(1, 30, 0)?); +/// assert_eq!(offset!(-1:30), UtcOffset::from_hms(-1, -30, 0)?); +/// assert_eq!(offset!(+1:30:59), UtcOffset::from_hms(1, 30, 59)?); +/// assert_eq!(offset!(-1:30:59), UtcOffset::from_hms(-1, -30, -59)?); +/// assert_eq!(offset!(+23:59:59), UtcOffset::from_hms(23, 59, 59)?); +/// assert_eq!(offset!(-23:59:59), UtcOffset::from_hms(-23, -59, -59)?); +/// # Ok::<_, time::Error>(()) +/// ``` +pub use time_macros::offset; +/// Construct a [`Time`](crate::Time) with a statically known value. +/// +/// The resulting expression can be used in `const` or `static` declarations. +/// +/// Hours and minutes must be provided, while seconds defaults to zero. AM/PM is allowed +/// (either uppercase or lowercase). Any number of subsecond digits may be provided (though any +/// past nine will be discarded). +/// +/// All components are validated at compile-time. An error will be raised if any value is +/// invalid. +/// +/// ```rust +/// # use time::{Time, macros::time}; +/// assert_eq!(time!(0:00), Time::from_hms(0, 0, 0)?); +/// assert_eq!(time!(1:02:03), Time::from_hms(1, 2, 3)?); +/// assert_eq!( +/// time!(1:02:03.004_005_006), +/// Time::from_hms_nano(1, 2, 3, 4_005_006)? +/// ); +/// assert_eq!(time!(12:00 am), Time::from_hms(0, 0, 0)?); +/// assert_eq!(time!(1:02:03 am), Time::from_hms(1, 2, 3)?); +/// assert_eq!( +/// time!(1:02:03.004_005_006 am), +/// Time::from_hms_nano(1, 2, 3, 4_005_006)? +/// ); +/// assert_eq!(time!(12 pm), Time::from_hms(12, 0, 0)?); +/// assert_eq!(time!(12:00 pm), Time::from_hms(12, 0, 0)?); +/// assert_eq!(time!(1:02:03 pm), Time::from_hms(13, 2, 3)?); +/// assert_eq!( +/// time!(1:02:03.004_005_006 pm), +/// Time::from_hms_nano(13, 2, 3, 4_005_006)? +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +pub use time_macros::time; diff --git a/third_party/rust/time/src/month.rs b/third_party/rust/time/src/month.rs new file mode 100644 index 0000000000..0c657e9821 --- /dev/null +++ b/third_party/rust/time/src/month.rs @@ -0,0 +1,164 @@ +//! The `Month` enum and its associated `impl`s. + +use core::fmt; +use core::num::NonZeroU8; +use core::str::FromStr; + +use self::Month::*; +use crate::error; + +/// Months of the year. +#[allow(clippy::missing_docs_in_private_items)] // variants +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Month { + January = 1, + February = 2, + March = 3, + April = 4, + May = 5, + June = 6, + July = 7, + August = 8, + September = 9, + October = 10, + November = 11, + December = 12, +} + +impl Month { + /// Create a `Month` from its numerical value. + pub(crate) const fn from_number(n: NonZeroU8) -> Result<Self, error::ComponentRange> { + match n.get() { + 1 => Ok(January), + 2 => Ok(February), + 3 => Ok(March), + 4 => Ok(April), + 5 => Ok(May), + 6 => Ok(June), + 7 => Ok(July), + 8 => Ok(August), + 9 => Ok(September), + 10 => Ok(October), + 11 => Ok(November), + 12 => Ok(December), + n => Err(error::ComponentRange { + name: "month", + minimum: 1, + maximum: 12, + value: n as _, + conditional_range: false, + }), + } + } + + /// Get the previous month. + /// + /// ```rust + /// # use time::Month; + /// assert_eq!(Month::January.previous(), Month::December); + /// ``` + pub const fn previous(self) -> Self { + match self { + January => December, + February => January, + March => February, + April => March, + May => April, + June => May, + July => June, + August => July, + September => August, + October => September, + November => October, + December => November, + } + } + + /// Get the next month. + /// + /// ```rust + /// # use time::Month; + /// assert_eq!(Month::January.next(), Month::February); + /// ``` + pub const fn next(self) -> Self { + match self { + January => February, + February => March, + March => April, + April => May, + May => June, + June => July, + July => August, + August => September, + September => October, + October => November, + November => December, + December => January, + } + } +} + +impl fmt::Display for Month { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + January => "January", + February => "February", + March => "March", + April => "April", + May => "May", + June => "June", + July => "July", + August => "August", + September => "September", + October => "October", + November => "November", + December => "December", + }) + } +} + +impl FromStr for Month { + type Err = error::InvalidVariant; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "January" => Ok(January), + "February" => Ok(February), + "March" => Ok(March), + "April" => Ok(April), + "May" => Ok(May), + "June" => Ok(June), + "July" => Ok(July), + "August" => Ok(August), + "September" => Ok(September), + "October" => Ok(October), + "November" => Ok(November), + "December" => Ok(December), + _ => Err(error::InvalidVariant), + } + } +} + +impl From<Month> for u8 { + fn from(month: Month) -> Self { + month as _ + } +} + +impl TryFrom<u8> for Month { + type Error = error::ComponentRange; + + fn try_from(value: u8) -> Result<Self, Self::Error> { + match NonZeroU8::new(value) { + Some(value) => Self::from_number(value), + None => Err(error::ComponentRange { + name: "month", + minimum: 1, + maximum: 12, + value: 0, + conditional_range: false, + }), + } + } +} diff --git a/third_party/rust/time/src/offset_date_time.rs b/third_party/rust/time/src/offset_date_time.rs new file mode 100644 index 0000000000..c39a2b1d05 --- /dev/null +++ b/third_party/rust/time/src/offset_date_time.rs @@ -0,0 +1,1398 @@ +//! The [`OffsetDateTime`] struct and its associated `impl`s. + +use core::cmp::Ordering; +#[cfg(feature = "std")] +use core::convert::From; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::ops::{Add, Sub}; +use core::time::Duration as StdDuration; +#[cfg(feature = "formatting")] +use std::io; +#[cfg(feature = "std")] +use std::time::SystemTime; + +use crate::date::{MAX_YEAR, MIN_YEAR}; +#[cfg(feature = "formatting")] +use crate::formatting::Formattable; +#[cfg(feature = "parsing")] +use crate::parsing::Parsable; +#[cfg(feature = "parsing")] +use crate::util; +use crate::{error, Date, Duration, Month, PrimitiveDateTime, Time, UtcOffset, Weekday}; + +/// The Julian day of the Unix epoch. +const UNIX_EPOCH_JULIAN_DAY: i32 = Date::__from_ordinal_date_unchecked(1970, 1).to_julian_day(); + +/// A [`PrimitiveDateTime`] with a [`UtcOffset`]. +/// +/// All comparisons are performed using the UTC time. +#[derive(Clone, Copy, Eq)] +pub struct OffsetDateTime { + /// The [`PrimitiveDateTime`], which is _always_ in the stored offset. + pub(crate) local_datetime: PrimitiveDateTime, + /// The [`UtcOffset`], which will be added to the [`PrimitiveDateTime`] as necessary. + pub(crate) offset: UtcOffset, +} + +impl OffsetDateTime { + /// Midnight, 1 January, 1970 (UTC). + /// + /// ```rust + /// # use time::OffsetDateTime; + /// # use time_macros::datetime; + /// assert_eq!(OffsetDateTime::UNIX_EPOCH, datetime!(1970-01-01 0:00 UTC),); + /// ``` + pub const UNIX_EPOCH: Self = Date::__from_ordinal_date_unchecked(1970, 1) + .midnight() + .assume_utc(); + + // region: now + /// Create a new `OffsetDateTime` with the current date and time in UTC. + /// + /// ```rust + /// # use time::OffsetDateTime; + /// # use time_macros::offset; + /// assert!(OffsetDateTime::now_utc().year() >= 2019); + /// assert_eq!(OffsetDateTime::now_utc().offset(), offset!(UTC)); + /// ``` + #[cfg(feature = "std")] + pub fn now_utc() -> Self { + #[cfg(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")), + feature = "wasm-bindgen" + ))] + { + js_sys::Date::new_0().into() + } + + #[cfg(not(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")), + feature = "wasm-bindgen" + )))] + SystemTime::now().into() + } + + /// Attempt to create a new `OffsetDateTime` with the current date and time in the local offset. + /// If the offset cannot be determined, an error is returned. + /// + /// ```rust + /// # use time::OffsetDateTime; + /// # if false { + /// assert!(OffsetDateTime::now_local().is_ok()); + /// # } + /// ``` + #[cfg(feature = "local-offset")] + pub fn now_local() -> Result<Self, error::IndeterminateOffset> { + let t = Self::now_utc(); + Ok(t.to_offset(UtcOffset::local_offset_at(t)?)) + } + // endregion now + + /// Convert the `OffsetDateTime` from the current [`UtcOffset`] to the provided [`UtcOffset`]. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!( + /// datetime!(2000-01-01 0:00 UTC) + /// .to_offset(offset!(-1)) + /// .year(), + /// 1999, + /// ); + /// + /// // Let's see what time Sydney's new year's celebration is in New York and Los Angeles. + /// + /// // Construct midnight on new year's in Sydney. + /// let sydney = datetime!(2000-01-01 0:00 +11); + /// let new_york = sydney.to_offset(offset!(-5)); + /// let los_angeles = sydney.to_offset(offset!(-8)); + /// assert_eq!(sydney.hour(), 0); + /// assert_eq!(new_york.hour(), 8); + /// assert_eq!(los_angeles.hour(), 5); + /// ``` + /// + /// # Panics + /// + /// This method panics if the local date-time in the new offset is outside the supported range. + pub const fn to_offset(self, offset: UtcOffset) -> Self { + if self.offset.whole_hours() == offset.whole_hours() + && self.offset.minutes_past_hour() == offset.minutes_past_hour() + && self.offset.seconds_past_minute() == offset.seconds_past_minute() + { + return self; + } + + let (year, ordinal, time) = self.to_offset_raw(offset); + + if year > MAX_YEAR || year < MIN_YEAR { + panic!("local datetime out of valid range"); + } + + Date::__from_ordinal_date_unchecked(year, ordinal) + .with_time(time) + .assume_offset(offset) + } + + /// Equivalent to `.to_offset(UtcOffset::UTC)`, but returning the year, ordinal, and time. This + /// avoids constructing an invalid [`Date`] if the new value is out of range. + const fn to_offset_raw(self, offset: UtcOffset) -> (i32, u16, Time) { + let from = self.offset; + let to = offset; + + // Fast path for when no conversion is necessary. + if from.whole_hours() == to.whole_hours() + && from.minutes_past_hour() == to.minutes_past_hour() + && from.seconds_past_minute() == to.seconds_past_minute() + { + return (self.year(), self.ordinal(), self.time()); + } + + let mut second = self.second() as i16 - from.seconds_past_minute() as i16 + + to.seconds_past_minute() as i16; + let mut minute = + self.minute() as i16 - from.minutes_past_hour() as i16 + to.minutes_past_hour() as i16; + let mut hour = self.hour() as i8 - from.whole_hours() + to.whole_hours(); + let (mut year, ordinal) = self.to_ordinal_date(); + let mut ordinal = ordinal as i16; + + // Cascade the values twice. This is needed because the values are adjusted twice above. + cascade!(second in 0..60 => minute); + cascade!(second in 0..60 => minute); + cascade!(minute in 0..60 => hour); + cascade!(minute in 0..60 => hour); + cascade!(hour in 0..24 => ordinal); + cascade!(hour in 0..24 => ordinal); + cascade!(ordinal => year); + + debug_assert!(ordinal > 0); + debug_assert!(ordinal <= crate::util::days_in_year(year) as i16); + + ( + year, + ordinal as _, + Time::__from_hms_nanos_unchecked( + hour as _, + minute as _, + second as _, + self.nanosecond(), + ), + ) + } + + // region: constructors + /// Create an `OffsetDateTime` from the provided Unix timestamp. Calling `.offset()` on the + /// resulting value is guaranteed to return UTC. + /// + /// ```rust + /// # use time::OffsetDateTime; + /// # use time_macros::datetime; + /// assert_eq!( + /// OffsetDateTime::from_unix_timestamp(0), + /// Ok(OffsetDateTime::UNIX_EPOCH), + /// ); + /// assert_eq!( + /// OffsetDateTime::from_unix_timestamp(1_546_300_800), + /// Ok(datetime!(2019-01-01 0:00 UTC)), + /// ); + /// ``` + /// + /// If you have a timestamp-nanosecond pair, you can use something along the lines of the + /// following: + /// + /// ```rust + /// # use time::{Duration, OffsetDateTime, ext::NumericalDuration}; + /// let (timestamp, nanos) = (1, 500_000_000); + /// assert_eq!( + /// OffsetDateTime::from_unix_timestamp(timestamp)? + Duration::nanoseconds(nanos), + /// OffsetDateTime::UNIX_EPOCH + 1.5.seconds() + /// ); + /// # Ok::<_, time::Error>(()) + /// ``` + pub const fn from_unix_timestamp(timestamp: i64) -> Result<Self, error::ComponentRange> { + #[allow(clippy::missing_docs_in_private_items)] + const MIN_TIMESTAMP: i64 = Date::MIN.midnight().assume_utc().unix_timestamp(); + #[allow(clippy::missing_docs_in_private_items)] + const MAX_TIMESTAMP: i64 = Date::MAX + .with_time(Time::__from_hms_nanos_unchecked(23, 59, 59, 999_999_999)) + .assume_utc() + .unix_timestamp(); + + ensure_value_in_range!(timestamp in MIN_TIMESTAMP => MAX_TIMESTAMP); + + // Use the unchecked method here, as the input validity has already been verified. + let date = Date::from_julian_day_unchecked( + UNIX_EPOCH_JULIAN_DAY + div_floor!(timestamp, 86_400) as i32, + ); + + let seconds_within_day = timestamp.rem_euclid(86_400); + let time = Time::__from_hms_nanos_unchecked( + (seconds_within_day / 3_600) as _, + ((seconds_within_day % 3_600) / 60) as _, + (seconds_within_day % 60) as _, + 0, + ); + + Ok(PrimitiveDateTime::new(date, time).assume_utc()) + } + + /// Construct an `OffsetDateTime` from the provided Unix timestamp (in nanoseconds). Calling + /// `.offset()` on the resulting value is guaranteed to return UTC. + /// + /// ```rust + /// # use time::OffsetDateTime; + /// # use time_macros::datetime; + /// assert_eq!( + /// OffsetDateTime::from_unix_timestamp_nanos(0), + /// Ok(OffsetDateTime::UNIX_EPOCH), + /// ); + /// assert_eq!( + /// OffsetDateTime::from_unix_timestamp_nanos(1_546_300_800_000_000_000), + /// Ok(datetime!(2019-01-01 0:00 UTC)), + /// ); + /// ``` + pub const fn from_unix_timestamp_nanos(timestamp: i128) -> Result<Self, error::ComponentRange> { + let datetime = const_try!(Self::from_unix_timestamp( + div_floor!(timestamp, 1_000_000_000) as i64 + )); + + Ok(datetime + .local_datetime + .replace_time(Time::__from_hms_nanos_unchecked( + datetime.local_datetime.hour(), + datetime.local_datetime.minute(), + datetime.local_datetime.second(), + timestamp.rem_euclid(1_000_000_000) as u32, + )) + .assume_utc()) + } + // endregion constructors + + // region: getters + /// Get the [`UtcOffset`]. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).offset(), offset!(UTC)); + /// assert_eq!(datetime!(2019-01-01 0:00 +1).offset(), offset!(+1)); + /// ``` + pub const fn offset(self) -> UtcOffset { + self.offset + } + + /// Get the [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time). + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(1970-01-01 0:00 UTC).unix_timestamp(), 0); + /// assert_eq!(datetime!(1970-01-01 0:00 -1).unix_timestamp(), 3_600); + /// ``` + pub const fn unix_timestamp(self) -> i64 { + let offset = self.offset.whole_seconds() as i64; + + let days = + (self.local_datetime.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * 86_400; + let hours = self.local_datetime.hour() as i64 * 3_600; + let minutes = self.local_datetime.minute() as i64 * 60; + let seconds = self.local_datetime.second() as i64; + days + hours + minutes + seconds - offset + } + + /// Get the Unix timestamp in nanoseconds. + /// + /// ```rust + /// use time_macros::datetime; + /// assert_eq!(datetime!(1970-01-01 0:00 UTC).unix_timestamp_nanos(), 0); + /// assert_eq!( + /// datetime!(1970-01-01 0:00 -1).unix_timestamp_nanos(), + /// 3_600_000_000_000, + /// ); + /// ``` + pub const fn unix_timestamp_nanos(self) -> i128 { + self.unix_timestamp() as i128 * 1_000_000_000 + self.nanosecond() as i128 + } + + /// Get the [`Date`] in the stored offset. + /// + /// ```rust + /// # use time_macros::{date, datetime, offset}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).date(), date!(2019-01-01)); + /// assert_eq!( + /// datetime!(2019-01-01 0:00 UTC) + /// .to_offset(offset!(-1)) + /// .date(), + /// date!(2018-12-31), + /// ); + /// ``` + pub const fn date(self) -> Date { + self.local_datetime.date() + } + + /// Get the [`Time`] in the stored offset. + /// + /// ```rust + /// # use time_macros::{datetime, offset, time}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).time(), time!(0:00)); + /// assert_eq!( + /// datetime!(2019-01-01 0:00 UTC) + /// .to_offset(offset!(-1)) + /// .time(), + /// time!(23:00) + /// ); + /// ``` + pub const fn time(self) -> Time { + self.local_datetime.time() + } + + // region: date getters + /// Get the year of the date in the stored offset. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).year(), 2019); + /// assert_eq!( + /// datetime!(2019-12-31 23:00 UTC) + /// .to_offset(offset!(+1)) + /// .year(), + /// 2020, + /// ); + /// assert_eq!(datetime!(2020-01-01 0:00 UTC).year(), 2020); + /// ``` + pub const fn year(self) -> i32 { + self.date().year() + } + + /// Get the month of the date in the stored offset. + /// + /// ```rust + /// # use time::Month; + /// # use time_macros::{datetime, offset}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).month(), Month::January); + /// assert_eq!( + /// datetime!(2019-12-31 23:00 UTC) + /// .to_offset(offset!(+1)) + /// .month(), + /// Month::January, + /// ); + /// ``` + pub const fn month(self) -> Month { + self.date().month() + } + + /// Get the day of the date in the stored offset. + /// + /// The returned value will always be in the range `1..=31`. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).day(), 1); + /// assert_eq!( + /// datetime!(2019-12-31 23:00 UTC) + /// .to_offset(offset!(+1)) + /// .day(), + /// 1, + /// ); + /// ``` + pub const fn day(self) -> u8 { + self.date().day() + } + + /// Get the day of the year of the date in the stored offset. + /// + /// The returned value will always be in the range `1..=366`. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).ordinal(), 1); + /// assert_eq!( + /// datetime!(2019-12-31 23:00 UTC) + /// .to_offset(offset!(+1)) + /// .ordinal(), + /// 1, + /// ); + /// ``` + pub const fn ordinal(self) -> u16 { + self.date().ordinal() + } + + /// Get the ISO week number of the date in the stored offset. + /// + /// The returned value will always be in the range `1..=53`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).iso_week(), 1); + /// assert_eq!(datetime!(2020-01-01 0:00 UTC).iso_week(), 1); + /// assert_eq!(datetime!(2020-12-31 0:00 UTC).iso_week(), 53); + /// assert_eq!(datetime!(2021-01-01 0:00 UTC).iso_week(), 53); + /// ``` + pub const fn iso_week(self) -> u8 { + self.date().iso_week() + } + + /// Get the week number where week 1 begins on the first Sunday. + /// + /// The returned value will always be in the range `0..=53`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).sunday_based_week(), 0); + /// assert_eq!(datetime!(2020-01-01 0:00 UTC).sunday_based_week(), 0); + /// assert_eq!(datetime!(2020-12-31 0:00 UTC).sunday_based_week(), 52); + /// assert_eq!(datetime!(2021-01-01 0:00 UTC).sunday_based_week(), 0); + /// ``` + pub const fn sunday_based_week(self) -> u8 { + self.date().sunday_based_week() + } + + /// Get the week number where week 1 begins on the first Monday. + /// + /// The returned value will always be in the range `0..=53`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).monday_based_week(), 0); + /// assert_eq!(datetime!(2020-01-01 0:00 UTC).monday_based_week(), 0); + /// assert_eq!(datetime!(2020-12-31 0:00 UTC).monday_based_week(), 52); + /// assert_eq!(datetime!(2021-01-01 0:00 UTC).monday_based_week(), 0); + /// ``` + pub const fn monday_based_week(self) -> u8 { + self.date().monday_based_week() + } + + /// Get the year, month, and day. + /// + /// ```rust + /// # use time::Month; + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2019-01-01 0:00 UTC).to_calendar_date(), + /// (2019, Month::January, 1) + /// ); + /// ``` + pub const fn to_calendar_date(self) -> (i32, Month, u8) { + self.date().to_calendar_date() + } + + /// Get the year and ordinal day number. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2019-01-01 0:00 UTC).to_ordinal_date(), + /// (2019, 1) + /// ); + /// ``` + pub const fn to_ordinal_date(self) -> (i32, u16) { + self.date().to_ordinal_date() + } + + /// Get the ISO 8601 year, week number, and weekday. + /// + /// ```rust + /// # use time::Weekday::*; + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2019-01-01 0:00 UTC).to_iso_week_date(), + /// (2019, 1, Tuesday) + /// ); + /// assert_eq!( + /// datetime!(2019-10-04 0:00 UTC).to_iso_week_date(), + /// (2019, 40, Friday) + /// ); + /// assert_eq!( + /// datetime!(2020-01-01 0:00 UTC).to_iso_week_date(), + /// (2020, 1, Wednesday) + /// ); + /// assert_eq!( + /// datetime!(2020-12-31 0:00 UTC).to_iso_week_date(), + /// (2020, 53, Thursday) + /// ); + /// assert_eq!( + /// datetime!(2021-01-01 0:00 UTC).to_iso_week_date(), + /// (2020, 53, Friday) + /// ); + /// ``` + pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { + self.date().to_iso_week_date() + } + + /// Get the weekday of the date in the stored offset. + /// + /// ```rust + /// # use time::Weekday::*; + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).weekday(), Tuesday); + /// assert_eq!(datetime!(2019-02-01 0:00 UTC).weekday(), Friday); + /// assert_eq!(datetime!(2019-03-01 0:00 UTC).weekday(), Friday); + /// ``` + pub const fn weekday(self) -> Weekday { + self.date().weekday() + } + + /// Get the Julian day for the date. The time is not taken into account for this calculation. + /// + /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is + /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(-4713-11-24 0:00 UTC).to_julian_day(), 0); + /// assert_eq!(datetime!(2000-01-01 0:00 UTC).to_julian_day(), 2_451_545); + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).to_julian_day(), 2_458_485); + /// assert_eq!(datetime!(2019-12-31 0:00 UTC).to_julian_day(), 2_458_849); + /// ``` + pub const fn to_julian_day(self) -> i32 { + self.date().to_julian_day() + } + // endregion date getters + + // region: time getters + /// Get the clock hour, minute, and second. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2020-01-01 0:00:00 UTC).to_hms(), (0, 0, 0)); + /// assert_eq!(datetime!(2020-01-01 23:59:59 UTC).to_hms(), (23, 59, 59)); + /// ``` + pub const fn to_hms(self) -> (u8, u8, u8) { + self.time().as_hms() + } + + /// Get the clock hour, minute, second, and millisecond. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2020-01-01 0:00:00 UTC).to_hms_milli(), + /// (0, 0, 0, 0) + /// ); + /// assert_eq!( + /// datetime!(2020-01-01 23:59:59.999 UTC).to_hms_milli(), + /// (23, 59, 59, 999) + /// ); + /// ``` + pub const fn to_hms_milli(self) -> (u8, u8, u8, u16) { + self.time().as_hms_milli() + } + + /// Get the clock hour, minute, second, and microsecond. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2020-01-01 0:00:00 UTC).to_hms_micro(), + /// (0, 0, 0, 0) + /// ); + /// assert_eq!( + /// datetime!(2020-01-01 23:59:59.999_999 UTC).to_hms_micro(), + /// (23, 59, 59, 999_999) + /// ); + /// ``` + pub const fn to_hms_micro(self) -> (u8, u8, u8, u32) { + self.time().as_hms_micro() + } + + /// Get the clock hour, minute, second, and nanosecond. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2020-01-01 0:00:00 UTC).to_hms_nano(), + /// (0, 0, 0, 0) + /// ); + /// assert_eq!( + /// datetime!(2020-01-01 23:59:59.999_999_999 UTC).to_hms_nano(), + /// (23, 59, 59, 999_999_999) + /// ); + /// ``` + pub const fn to_hms_nano(self) -> (u8, u8, u8, u32) { + self.time().as_hms_nano() + } + + /// Get the clock hour in the stored offset. + /// + /// The returned value will always be in the range `0..24`. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).hour(), 0); + /// assert_eq!( + /// datetime!(2019-01-01 23:59:59 UTC) + /// .to_offset(offset!(-2)) + /// .hour(), + /// 21, + /// ); + /// ``` + pub const fn hour(self) -> u8 { + self.time().hour() + } + + /// Get the minute within the hour in the stored offset. + /// + /// The returned value will always be in the range `0..60`. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).minute(), 0); + /// assert_eq!( + /// datetime!(2019-01-01 23:59:59 UTC) + /// .to_offset(offset!(+0:30)) + /// .minute(), + /// 29, + /// ); + /// ``` + pub const fn minute(self) -> u8 { + self.time().minute() + } + + /// Get the second within the minute in the stored offset. + /// + /// The returned value will always be in the range `0..60`. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).second(), 0); + /// assert_eq!( + /// datetime!(2019-01-01 23:59:59 UTC) + /// .to_offset(offset!(+0:00:30)) + /// .second(), + /// 29, + /// ); + /// ``` + pub const fn second(self) -> u8 { + self.time().second() + } + + // Because a `UtcOffset` is limited in resolution to one second, any subsecond value will not + // change when adjusting for the offset. + + /// Get the milliseconds within the second in the stored offset. + /// + /// The returned value will always be in the range `0..1_000`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).millisecond(), 0); + /// assert_eq!(datetime!(2019-01-01 23:59:59.999 UTC).millisecond(), 999); + /// ``` + pub const fn millisecond(self) -> u16 { + self.time().millisecond() + } + + /// Get the microseconds within the second in the stored offset. + /// + /// The returned value will always be in the range `0..1_000_000`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).microsecond(), 0); + /// assert_eq!( + /// datetime!(2019-01-01 23:59:59.999_999 UTC).microsecond(), + /// 999_999, + /// ); + /// ``` + pub const fn microsecond(self) -> u32 { + self.time().microsecond() + } + + /// Get the nanoseconds within the second in the stored offset. + /// + /// The returned value will always be in the range `0..1_000_000_000`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00 UTC).nanosecond(), 0); + /// assert_eq!( + /// datetime!(2019-01-01 23:59:59.999_999_999 UTC).nanosecond(), + /// 999_999_999, + /// ); + /// ``` + pub const fn nanosecond(self) -> u32 { + self.time().nanosecond() + } + // endregion time getters + // endregion getters + + // region: checked arithmetic + /// Computes `self + duration`, returning `None` if an overflow occurred. + /// + /// ``` + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::{datetime, offset}; + /// let datetime = Date::MIN.midnight().assume_offset(offset!(+10)); + /// assert_eq!(datetime.checked_add((-2).days()), None); + /// + /// let datetime = Date::MAX.midnight().assume_offset(offset!(+10)); + /// assert_eq!(datetime.checked_add(2.days()), None); + /// + /// assert_eq!( + /// datetime!(2019 - 11 - 25 15:30 +10).checked_add(27.hours()), + /// Some(datetime!(2019 - 11 - 26 18:30 +10)) + /// ); + /// ``` + pub const fn checked_add(self, duration: Duration) -> Option<Self> { + Some(const_try_opt!(self.local_datetime.checked_add(duration)).assume_offset(self.offset)) + } + + /// Computes `self - duration`, returning `None` if an overflow occurred. + /// + /// ``` + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::{datetime, offset}; + /// let datetime = Date::MIN.midnight().assume_offset(offset!(+10)); + /// assert_eq!(datetime.checked_sub(2.days()), None); + /// + /// let datetime = Date::MAX.midnight().assume_offset(offset!(+10)); + /// assert_eq!(datetime.checked_sub((-2).days()), None); + /// + /// assert_eq!( + /// datetime!(2019 - 11 - 25 15:30 +10).checked_sub(27.hours()), + /// Some(datetime!(2019 - 11 - 24 12:30 +10)) + /// ); + /// ``` + pub const fn checked_sub(self, duration: Duration) -> Option<Self> { + Some(const_try_opt!(self.local_datetime.checked_sub(duration)).assume_offset(self.offset)) + } + // endregion: checked arithmetic + + // region: saturating arithmetic + /// Computes `self + duration`, saturating value on overflow. + /// + /// ``` + /// # use time::ext::NumericalDuration; + /// # use time_macros::datetime; + /// assert_eq!( + #[cfg_attr( + feature = "large-dates", + doc = " datetime!(-999999-01-01 0:00 +10).saturating_add((-2).days())," + )] + #[cfg_attr(feature = "large-dates", doc = " datetime!(-999999-01-01 0:00 +10)")] + #[cfg_attr( + not(feature = "large-dates"), + doc = " datetime!(-9999-01-01 0:00 +10).saturating_add((-2).days())," + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = " datetime!(-9999-01-01 0:00 +10)" + )] + /// ); + /// + /// assert_eq!( + #[cfg_attr( + feature = "large-dates", + doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10).saturating_add(2.days())," + )] + #[cfg_attr( + feature = "large-dates", + doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10)" + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10).saturating_add(2.days())," + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10)" + )] + /// ); + /// + /// assert_eq!( + /// datetime!(2019 - 11 - 25 15:30 +10).saturating_add(27.hours()), + /// datetime!(2019 - 11 - 26 18:30 +10) + /// ); + /// ``` + pub const fn saturating_add(self, duration: Duration) -> Self { + if let Some(datetime) = self.checked_add(duration) { + datetime + } else if duration.is_negative() { + PrimitiveDateTime::MIN.assume_offset(self.offset) + } else { + debug_assert!(duration.is_positive()); + PrimitiveDateTime::MAX.assume_offset(self.offset) + } + } + + /// Computes `self - duration`, saturating value on overflow. + /// + /// ``` + /// # use time::ext::NumericalDuration; + /// # use time_macros::datetime; + /// assert_eq!( + #[cfg_attr( + feature = "large-dates", + doc = " datetime!(-999999-01-01 0:00 +10).saturating_sub(2.days())," + )] + #[cfg_attr(feature = "large-dates", doc = " datetime!(-999999-01-01 0:00 +10)")] + #[cfg_attr( + not(feature = "large-dates"), + doc = " datetime!(-9999-01-01 0:00 +10).saturating_sub(2.days())," + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = " datetime!(-9999-01-01 0:00 +10)" + )] + /// ); + /// + /// assert_eq!( + #[cfg_attr( + feature = "large-dates", + doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10).saturating_sub((-2).days())," + )] + #[cfg_attr( + feature = "large-dates", + doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10)" + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10).saturating_sub((-2).days())," + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10)" + )] + /// ); + /// + /// assert_eq!( + /// datetime!(2019 - 11 - 25 15:30 +10).saturating_sub(27.hours()), + /// datetime!(2019 - 11 - 24 12:30 +10) + /// ); + /// ``` + pub const fn saturating_sub(self, duration: Duration) -> Self { + if let Some(datetime) = self.checked_sub(duration) { + datetime + } else if duration.is_negative() { + PrimitiveDateTime::MAX.assume_offset(self.offset) + } else { + debug_assert!(duration.is_positive()); + PrimitiveDateTime::MIN.assume_offset(self.offset) + } + } + // endregion: saturating arithmetic +} + +// region: replacement +/// Methods that replace part of the `OffsetDateTime`. +impl OffsetDateTime { + /// Replace the time, which is assumed to be in the stored offset. The date and offset + /// components are unchanged. + /// + /// ```rust + /// # use time_macros::{datetime, time}; + /// assert_eq!( + /// datetime!(2020-01-01 5:00 UTC).replace_time(time!(12:00)), + /// datetime!(2020-01-01 12:00 UTC) + /// ); + /// assert_eq!( + /// datetime!(2020-01-01 12:00 -5).replace_time(time!(7:00)), + /// datetime!(2020-01-01 7:00 -5) + /// ); + /// assert_eq!( + /// datetime!(2020-01-01 0:00 +1).replace_time(time!(12:00)), + /// datetime!(2020-01-01 12:00 +1) + /// ); + /// ``` + #[must_use = "This method does not mutate the original `OffsetDateTime`."] + pub const fn replace_time(self, time: Time) -> Self { + self.local_datetime + .replace_time(time) + .assume_offset(self.offset) + } + + /// Replace the date, which is assumed to be in the stored offset. The time and offset + /// components are unchanged. + /// + /// ```rust + /// # use time_macros::{datetime, date}; + /// assert_eq!( + /// datetime!(2020-01-01 12:00 UTC).replace_date(date!(2020-01-30)), + /// datetime!(2020-01-30 12:00 UTC) + /// ); + /// assert_eq!( + /// datetime!(2020-01-01 0:00 +1).replace_date(date!(2020-01-30)), + /// datetime!(2020-01-30 0:00 +1) + /// ); + /// ``` + #[must_use = "This method does not mutate the original `OffsetDateTime`."] + pub const fn replace_date(self, date: Date) -> Self { + self.local_datetime + .replace_date(date) + .assume_offset(self.offset) + } + + /// Replace the date and time, which are assumed to be in the stored offset. The offset + /// component remains unchanged. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2020-01-01 12:00 UTC).replace_date_time(datetime!(2020-01-30 16:00)), + /// datetime!(2020-01-30 16:00 UTC) + /// ); + /// assert_eq!( + /// datetime!(2020-01-01 12:00 +1).replace_date_time(datetime!(2020-01-30 0:00)), + /// datetime!(2020-01-30 0:00 +1) + /// ); + /// ``` + #[must_use = "This method does not mutate the original `OffsetDateTime`."] + pub const fn replace_date_time(self, date_time: PrimitiveDateTime) -> Self { + date_time.assume_offset(self.offset) + } + + /// Replace the offset. The date and time components remain unchanged. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!( + /// datetime!(2020-01-01 0:00 UTC).replace_offset(offset!(-5)), + /// datetime!(2020-01-01 0:00 -5) + /// ); + /// ``` + #[must_use = "This method does not mutate the original `OffsetDateTime`."] + pub const fn replace_offset(self, offset: UtcOffset) -> Self { + self.local_datetime.assume_offset(offset) + } + + /// Replace the year. The month and day will be unchanged. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 12:00 +01).replace_year(2019), + /// Ok(datetime!(2019 - 02 - 18 12:00 +01)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year + /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year + /// ``` + pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> { + Ok(const_try!(self.local_datetime.replace_year(year)).assume_offset(self.offset)) + } + + /// Replace the month of the year. + /// + /// ```rust + /// # use time_macros::datetime; + /// # use time::Month; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 12:00 +01).replace_month(Month::January), + /// Ok(datetime!(2022 - 01 - 18 12:00 +01)) + /// ); + /// assert!(datetime!(2022 - 01 - 30 12:00 +01).replace_month(Month::February).is_err()); // 30 isn't a valid day in February + /// ``` + pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> { + Ok(const_try!(self.local_datetime.replace_month(month)).assume_offset(self.offset)) + } + + /// Replace the day of the month. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 12:00 +01).replace_day(1), + /// Ok(datetime!(2022 - 02 - 01 12:00 +01)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_day(0).is_err()); // 00 isn't a valid day + /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_day(30).is_err()); // 30 isn't a valid day in February + /// ``` + pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> { + Ok(const_try!(self.local_datetime.replace_day(day)).assume_offset(self.offset)) + } + + /// Replace the clock hour. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_hour(7), + /// Ok(datetime!(2022 - 02 - 18 07:02:03.004_005_006 +01)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_hour(24).is_err()); // 24 isn't a valid hour + /// ``` + pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> { + Ok(const_try!(self.local_datetime.replace_hour(hour)).assume_offset(self.offset)) + } + + /// Replace the minutes within the hour. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_minute(7), + /// Ok(datetime!(2022 - 02 - 18 01:07:03.004_005_006 +01)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_minute(60).is_err()); // 60 isn't a valid minute + /// ``` + pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> { + Ok(const_try!(self.local_datetime.replace_minute(minute)).assume_offset(self.offset)) + } + + /// Replace the seconds within the minute. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_second(7), + /// Ok(datetime!(2022 - 02 - 18 01:02:07.004_005_006 +01)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_second(60).is_err()); // 60 isn't a valid second + /// ``` + pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> { + Ok(const_try!(self.local_datetime.replace_second(second)).assume_offset(self.offset)) + } + + /// Replace the milliseconds within the second. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_millisecond(7), + /// Ok(datetime!(2022 - 02 - 18 01:02:03.007 +01)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond + /// ``` + pub const fn replace_millisecond( + self, + millisecond: u16, + ) -> Result<Self, error::ComponentRange> { + Ok( + const_try!(self.local_datetime.replace_millisecond(millisecond)) + .assume_offset(self.offset), + ) + } + + /// Replace the microseconds within the second. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_microsecond(7_008), + /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008 +01)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond + /// ``` + pub const fn replace_microsecond( + self, + microsecond: u32, + ) -> Result<Self, error::ComponentRange> { + Ok( + const_try!(self.local_datetime.replace_microsecond(microsecond)) + .assume_offset(self.offset), + ) + } + + /// Replace the nanoseconds within the second. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_nanosecond(7_008_009), + /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008_009 +01)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond + /// ``` + pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> { + Ok( + const_try!(self.local_datetime.replace_nanosecond(nanosecond)) + .assume_offset(self.offset), + ) + } +} +// endregion replacement + +// region: formatting & parsing +#[cfg(feature = "formatting")] +impl OffsetDateTime { + /// Format the `OffsetDateTime` using the provided [format + /// description](crate::format_description). + pub fn format_into( + self, + output: &mut impl io::Write, + format: &(impl Formattable + ?Sized), + ) -> Result<usize, error::Format> { + format.format_into( + output, + Some(self.date()), + Some(self.time()), + Some(self.offset), + ) + } + + /// Format the `OffsetDateTime` using the provided [format + /// description](crate::format_description). + /// + /// ```rust + /// # use time::format_description; + /// # use time_macros::datetime; + /// let format = format_description::parse( + /// "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \ + /// sign:mandatory]:[offset_minute]:[offset_second]", + /// )?; + /// assert_eq!( + /// datetime!(2020-01-02 03:04:05 +06:07:08).format(&format)?, + /// "2020-01-02 03:04:05 +06:07:08" + /// ); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { + format.format(Some(self.date()), Some(self.time()), Some(self.offset)) + } +} + +#[cfg(feature = "parsing")] +impl OffsetDateTime { + /// Parse an `OffsetDateTime` from the input using the provided [format + /// description](crate::format_description). + /// + /// ```rust + /// # use time::OffsetDateTime; + /// # use time_macros::{datetime, format_description}; + /// let format = format_description!( + /// "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \ + /// sign:mandatory]:[offset_minute]:[offset_second]" + /// ); + /// assert_eq!( + /// OffsetDateTime::parse("2020-01-02 03:04:05 +06:07:08", &format)?, + /// datetime!(2020-01-02 03:04:05 +06:07:08) + /// ); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn parse( + input: &str, + description: &(impl Parsable + ?Sized), + ) -> Result<Self, error::Parse> { + description.parse_offset_date_time(input.as_bytes()) + } + + /// A helper method to check if the `OffsetDateTime` is a valid representation of a leap second. + /// Leap seconds, when parsed, are represented as the preceding nanosecond. However, leap + /// seconds can only occur as the last second of a month UTC. + pub(crate) const fn is_valid_leap_second_stand_in(self) -> bool { + // This comparison doesn't need to be adjusted for the stored offset, so check it first for + // speed. + if self.nanosecond() != 999_999_999 { + return false; + } + + let (year, ordinal, time) = self.to_offset_raw(UtcOffset::UTC); + + let date = match Date::from_ordinal_date(year, ordinal) { + Ok(date) => date, + Err(_) => return false, + }; + + time.hour() == 23 + && time.minute() == 59 + && time.second() == 59 + && date.day() == util::days_in_year_month(year, date.month()) + } +} + +impl fmt::Display for OffsetDateTime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {} {}", self.date(), self.time(), self.offset) + } +} + +impl fmt::Debug for OffsetDateTime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} +// endregion formatting & parsing + +// region: trait impls +impl PartialEq for OffsetDateTime { + fn eq(&self, rhs: &Self) -> bool { + self.to_offset_raw(UtcOffset::UTC) == rhs.to_offset_raw(UtcOffset::UTC) + } +} + +impl PartialOrd for OffsetDateTime { + fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { + Some(self.cmp(rhs)) + } +} + +impl Ord for OffsetDateTime { + fn cmp(&self, rhs: &Self) -> Ordering { + self.to_offset_raw(UtcOffset::UTC) + .cmp(&rhs.to_offset_raw(UtcOffset::UTC)) + } +} + +impl Hash for OffsetDateTime { + fn hash<H: Hasher>(&self, hasher: &mut H) { + // We need to distinguish this from a `PrimitiveDateTime`, which would otherwise conflict. + hasher.write(b"OffsetDateTime"); + self.to_offset_raw(UtcOffset::UTC).hash(hasher); + } +} + +impl<T> Add<T> for OffsetDateTime +where + PrimitiveDateTime: Add<T, Output = PrimitiveDateTime>, +{ + type Output = Self; + + fn add(self, rhs: T) -> Self::Output { + (self.local_datetime + rhs).assume_offset(self.offset) + } +} + +impl_add_assign!(OffsetDateTime: Duration, StdDuration); + +impl<T> Sub<T> for OffsetDateTime +where + PrimitiveDateTime: Sub<T, Output = PrimitiveDateTime>, +{ + type Output = Self; + + fn sub(self, rhs: T) -> Self::Output { + (self.local_datetime - rhs).assume_offset(self.offset) + } +} + +impl_sub_assign!(OffsetDateTime: Duration, StdDuration); + +impl Sub for OffsetDateTime { + type Output = Duration; + + fn sub(self, rhs: Self) -> Self::Output { + let adjustment = + Duration::seconds((self.offset.whole_seconds() - rhs.offset.whole_seconds()) as i64); + self.local_datetime - rhs.local_datetime - adjustment + } +} + +#[cfg(feature = "std")] +impl Add<Duration> for SystemTime { + type Output = Self; + + fn add(self, duration: Duration) -> Self::Output { + if duration.is_zero() { + self + } else if duration.is_positive() { + self + duration.unsigned_abs() + } else { + debug_assert!(duration.is_negative()); + self - duration.unsigned_abs() + } + } +} + +impl_add_assign!(SystemTime: #[cfg(feature = "std")] Duration); + +#[cfg(feature = "std")] +impl Sub<Duration> for SystemTime { + type Output = Self; + + fn sub(self, duration: Duration) -> Self::Output { + (OffsetDateTime::from(self) - duration).into() + } +} + +impl_sub_assign!(SystemTime: #[cfg(feature = "std")] Duration); + +#[cfg(feature = "std")] +impl Sub<SystemTime> for OffsetDateTime { + type Output = Duration; + + fn sub(self, rhs: SystemTime) -> Self::Output { + self - Self::from(rhs) + } +} + +#[cfg(feature = "std")] +impl Sub<OffsetDateTime> for SystemTime { + type Output = Duration; + + fn sub(self, rhs: OffsetDateTime) -> Self::Output { + OffsetDateTime::from(self) - rhs + } +} + +#[cfg(feature = "std")] +impl PartialEq<SystemTime> for OffsetDateTime { + fn eq(&self, rhs: &SystemTime) -> bool { + self == &Self::from(*rhs) + } +} + +#[cfg(feature = "std")] +impl PartialEq<OffsetDateTime> for SystemTime { + fn eq(&self, rhs: &OffsetDateTime) -> bool { + &OffsetDateTime::from(*self) == rhs + } +} + +#[cfg(feature = "std")] +impl PartialOrd<SystemTime> for OffsetDateTime { + fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> { + self.partial_cmp(&Self::from(*other)) + } +} + +#[cfg(feature = "std")] +impl PartialOrd<OffsetDateTime> for SystemTime { + fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> { + OffsetDateTime::from(*self).partial_cmp(other) + } +} + +#[cfg(feature = "std")] +impl From<SystemTime> for OffsetDateTime { + fn from(system_time: SystemTime) -> Self { + match system_time.duration_since(SystemTime::UNIX_EPOCH) { + Ok(duration) => Self::UNIX_EPOCH + duration, + Err(err) => Self::UNIX_EPOCH - err.duration(), + } + } +} + +#[allow(clippy::fallible_impl_from)] // caused by `debug_assert!` +#[cfg(feature = "std")] +impl From<OffsetDateTime> for SystemTime { + fn from(datetime: OffsetDateTime) -> Self { + let duration = datetime - OffsetDateTime::UNIX_EPOCH; + + if duration.is_zero() { + Self::UNIX_EPOCH + } else if duration.is_positive() { + Self::UNIX_EPOCH + duration.unsigned_abs() + } else { + debug_assert!(duration.is_negative()); + Self::UNIX_EPOCH - duration.unsigned_abs() + } + } +} + +#[allow(clippy::fallible_impl_from)] +#[cfg(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")), + feature = "wasm-bindgen" +))] +impl From<js_sys::Date> for OffsetDateTime { + fn from(js_date: js_sys::Date) -> Self { + // get_time() returns milliseconds + let timestamp_nanos = (js_date.get_time() * 1_000_000.0) as i128; + Self::from_unix_timestamp_nanos(timestamp_nanos) + .expect("invalid timestamp: Timestamp cannot fit in range") + } +} + +#[cfg(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")), + feature = "wasm-bindgen" +))] +impl From<OffsetDateTime> for js_sys::Date { + fn from(datetime: OffsetDateTime) -> Self { + // new Date() takes milliseconds + let timestamp = (datetime.unix_timestamp_nanos() / 1_000_000) as f64; + js_sys::Date::new(×tamp.into()) + } +} + +// endregion trait impls diff --git a/third_party/rust/time/src/parsing/combinator/mod.rs b/third_party/rust/time/src/parsing/combinator/mod.rs new file mode 100644 index 0000000000..3b4bc7a81b --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/mod.rs @@ -0,0 +1,192 @@ +//! Implementations of the low-level parser combinators. + +pub(crate) mod rfc; + +use crate::format_description::modifier::Padding; +use crate::parsing::shim::{Integer, IntegerParseBytes}; +use crate::parsing::ParsedItem; + +/// Parse a "+" or "-" sign. Returns the ASCII byte representing the sign, if present. +pub(crate) const fn sign(input: &[u8]) -> Option<ParsedItem<'_, u8>> { + match input { + [sign @ (b'-' | b'+'), remaining @ ..] => Some(ParsedItem(remaining, *sign)), + _ => None, + } +} + +/// Consume the first matching item, returning its associated value. +pub(crate) fn first_match<'a, T>( + options: impl IntoIterator<Item = (&'a [u8], T)>, + case_sensitive: bool, +) -> impl FnMut(&'a [u8]) -> Option<ParsedItem<'a, T>> { + let mut options = options.into_iter(); + move |input| { + options.find_map(|(expected, t)| { + if case_sensitive { + Some(ParsedItem(input.strip_prefix(expected)?, t)) + } else { + let n = expected.len(); + if n <= input.len() { + let (head, tail) = input.split_at(n); + if head.eq_ignore_ascii_case(expected) { + return Some(ParsedItem(tail, t)); + } + } + None + } + }) + } +} + +/// Consume zero or more instances of the provided parser. The parser must return the unit value. +pub(crate) fn zero_or_more<'a, P: Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>>( + parser: P, +) -> impl FnMut(&'a [u8]) -> ParsedItem<'a, ()> { + move |mut input| { + while let Some(remaining) = parser(input) { + input = remaining.into_inner(); + } + ParsedItem(input, ()) + } +} + +/// Consume one of or more instances of the provided parser. The parser must produce the unit value. +pub(crate) fn one_or_more<'a, P: Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>>( + parser: P, +) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>> { + move |mut input| { + input = parser(input)?.into_inner(); + while let Some(remaining) = parser(input) { + input = remaining.into_inner(); + } + Some(ParsedItem(input, ())) + } +} + +/// Consume between `n` and `m` instances of the provided parser. +pub(crate) fn n_to_m< + 'a, + const N: u8, + const M: u8, + T, + P: Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>, +>( + parser: P, +) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, &'a [u8]>> { + debug_assert!(M >= N); + move |mut input| { + // We need to keep this to determine the total length eventually consumed. + let orig_input = input; + + // Mandatory + for _ in 0..N { + input = parser(input)?.0; + } + + // Optional + for _ in N..M { + match parser(input) { + Some(parsed) => input = parsed.0, + None => break, + } + } + + Some(ParsedItem( + input, + &orig_input[..(orig_input.len() - input.len())], + )) + } +} + +/// Consume between `n` and `m` digits, returning the numerical value. +pub(crate) fn n_to_m_digits<const N: u8, const M: u8, T: Integer>( + input: &[u8], +) -> Option<ParsedItem<'_, T>> { + debug_assert!(M >= N); + n_to_m::<N, M, _, _>(any_digit)(input)?.flat_map(|value| value.parse_bytes()) +} + +/// Consume exactly `n` digits, returning the numerical value. +pub(crate) fn exactly_n_digits<const N: u8, T: Integer>(input: &[u8]) -> Option<ParsedItem<'_, T>> { + n_to_m_digits::<N, N, _>(input) +} + +/// Consume exactly `n` digits, returning the numerical value. +pub(crate) fn exactly_n_digits_padded<'a, const N: u8, T: Integer>( + padding: Padding, +) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> { + n_to_m_digits_padded::<N, N, _>(padding) +} + +/// Consume between `n` and `m` digits, returning the numerical value. +pub(crate) fn n_to_m_digits_padded<'a, const N: u8, const M: u8, T: Integer>( + padding: Padding, +) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> { + debug_assert!(M >= N); + move |mut input| match padding { + Padding::None => n_to_m_digits::<1, M, _>(input), + Padding::Space => { + debug_assert!(N > 0); + + let mut orig_input = input; + for _ in 0..(N - 1) { + match ascii_char::<b' '>(input) { + Some(parsed) => input = parsed.0, + None => break, + } + } + let pad_width = (orig_input.len() - input.len()) as u8; + + orig_input = input; + for _ in 0..(N - pad_width) { + input = any_digit(input)?.0; + } + for _ in N..M { + match any_digit(input) { + Some(parsed) => input = parsed.0, + None => break, + } + } + + ParsedItem(input, &orig_input[..(orig_input.len() - input.len())]) + .flat_map(|value| value.parse_bytes()) + } + Padding::Zero => n_to_m_digits::<N, M, _>(input), + } +} + +/// Consume exactly one digit. +pub(crate) const fn any_digit(input: &[u8]) -> Option<ParsedItem<'_, u8>> { + match input { + [c, remaining @ ..] if c.is_ascii_digit() => Some(ParsedItem(remaining, *c)), + _ => None, + } +} + +/// Consume exactly one of the provided ASCII characters. +pub(crate) fn ascii_char<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> { + debug_assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace()); + match input { + [c, remaining @ ..] if *c == CHAR => Some(ParsedItem(remaining, ())), + _ => None, + } +} + +/// Consume exactly one of the provided ASCII characters, case-insensitive. +pub(crate) fn ascii_char_ignore_case<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> { + debug_assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace()); + match input { + [c, remaining @ ..] if c.eq_ignore_ascii_case(&CHAR) => Some(ParsedItem(remaining, ())), + _ => None, + } +} + +/// Optionally consume an input with a given parser. +pub(crate) fn opt<'a, T>( + parser: impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>, +) -> impl Fn(&'a [u8]) -> ParsedItem<'a, Option<T>> { + move |input| match parser(input) { + Some(value) => value.map(Some), + None => ParsedItem(input, None), + } +} diff --git a/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs b/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs new file mode 100644 index 0000000000..613a9057ff --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs @@ -0,0 +1,173 @@ +//! Rules defined in [ISO 8601]. +//! +//! [ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html + +use core::num::{NonZeroU16, NonZeroU8}; + +use crate::parsing::combinator::{any_digit, ascii_char, exactly_n_digits, first_match, sign}; +use crate::parsing::ParsedItem; +use crate::{Month, Weekday}; + +/// What kind of format is being parsed. This is used to ensure each part of the format (date, time, +/// offset) is the same kind. +#[derive(Debug, Clone, Copy)] +pub(crate) enum ExtendedKind { + /// The basic format. + Basic, + /// The extended format. + Extended, + /// ¯\_(ツ)_/¯ + Unknown, +} + +impl ExtendedKind { + /// Is it possible that the format is extended? + pub(crate) const fn maybe_extended(self) -> bool { + matches!(self, Self::Extended | Self::Unknown) + } + + /// Is the format known for certain to be extended? + pub(crate) const fn is_extended(self) -> bool { + matches!(self, Self::Extended) + } + + /// If the kind is `Unknown`, make it `Basic`. Otherwise, do nothing. Returns `Some` if and only + /// if the kind is now `Basic`. + pub(crate) fn coerce_basic(&mut self) -> Option<()> { + match self { + Self::Basic => Some(()), + Self::Extended => None, + Self::Unknown => { + *self = Self::Basic; + Some(()) + } + } + } + + /// If the kind is `Unknown`, make it `Extended`. Otherwise, do nothing. Returns `Some` if and + /// only if the kind is now `Extended`. + pub(crate) fn coerce_extended(&mut self) -> Option<()> { + match self { + Self::Basic => None, + Self::Extended => Some(()), + Self::Unknown => { + *self = Self::Extended; + Some(()) + } + } + } +} + +/// Parse a possibly expanded year. +pub(crate) fn year(input: &[u8]) -> Option<ParsedItem<'_, i32>> { + Some(match sign(input) { + Some(ParsedItem(input, sign)) => exactly_n_digits::<6, u32>(input)?.map(|val| { + let val = val as i32; + if sign == b'-' { -val } else { val } + }), + None => exactly_n_digits::<4, u32>(input)?.map(|val| val as _), + }) +} + +/// Parse a month. +pub(crate) fn month(input: &[u8]) -> Option<ParsedItem<'_, Month>> { + first_match( + [ + (b"01".as_slice(), Month::January), + (b"02".as_slice(), Month::February), + (b"03".as_slice(), Month::March), + (b"04".as_slice(), Month::April), + (b"05".as_slice(), Month::May), + (b"06".as_slice(), Month::June), + (b"07".as_slice(), Month::July), + (b"08".as_slice(), Month::August), + (b"09".as_slice(), Month::September), + (b"10".as_slice(), Month::October), + (b"11".as_slice(), Month::November), + (b"12".as_slice(), Month::December), + ], + true, + )(input) +} + +/// Parse a week number. +pub(crate) fn week(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU8>> { + exactly_n_digits::<2, _>(input) +} + +/// Parse a day of the month. +pub(crate) fn day(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU8>> { + exactly_n_digits::<2, _>(input) +} + +/// Parse a day of the week. +pub(crate) fn dayk(input: &[u8]) -> Option<ParsedItem<'_, Weekday>> { + first_match( + [ + (b"1".as_slice(), Weekday::Monday), + (b"2".as_slice(), Weekday::Tuesday), + (b"3".as_slice(), Weekday::Wednesday), + (b"4".as_slice(), Weekday::Thursday), + (b"5".as_slice(), Weekday::Friday), + (b"6".as_slice(), Weekday::Saturday), + (b"7".as_slice(), Weekday::Sunday), + ], + true, + )(input) +} + +/// Parse a day of the year. +pub(crate) fn dayo(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU16>> { + exactly_n_digits::<3, _>(input) +} + +/// Parse the hour. +pub(crate) fn hour(input: &[u8]) -> Option<ParsedItem<'_, u8>> { + exactly_n_digits::<2, _>(input) +} + +/// Parse the minute. +pub(crate) fn min(input: &[u8]) -> Option<ParsedItem<'_, u8>> { + exactly_n_digits::<2, _>(input) +} + +/// Parse a floating point number as its integer and optional fractional parts. +/// +/// The number must have two digits before the decimal point. If a decimal point is present, at +/// least one digit must follow. +/// +/// The return type is a tuple of the integer part and optional fraction part. +pub(crate) fn float(input: &[u8]) -> Option<ParsedItem<'_, (u8, Option<f64>)>> { + // Two digits before the decimal. + let ParsedItem(input, integer_part) = match input { + [ + first_digit @ b'0'..=b'9', + second_digit @ b'0'..=b'9', + input @ .., + ] => ParsedItem(input, (first_digit - b'0') * 10 + (second_digit - b'0')), + _ => return None, + }; + + if let Some(ParsedItem(input, ())) = decimal_sign(input) { + // Mandatory post-decimal digit. + let ParsedItem(mut input, mut fractional_part) = + any_digit(input)?.map(|digit| ((digit - b'0') as f64) / 10.); + + let mut divisor = 10.; + // Any number of subsequent digits. + while let Some(ParsedItem(new_input, digit)) = any_digit(input) { + input = new_input; + divisor *= 10.; + fractional_part += (digit - b'0') as f64 / divisor; + } + + Some(ParsedItem(input, (integer_part, Some(fractional_part)))) + } else { + Some(ParsedItem(input, (integer_part, None))) + } +} + +/// Parse a "decimal sign", which is either a comma or a period. +fn decimal_sign(input: &[u8]) -> Option<ParsedItem<'_, ()>> { + ascii_char::<b'.'>(input).or_else(|| ascii_char::<b','>(input)) +} diff --git a/third_party/rust/time/src/parsing/combinator/rfc/mod.rs b/third_party/rust/time/src/parsing/combinator/rfc/mod.rs new file mode 100644 index 0000000000..2974a4d5c4 --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/rfc/mod.rs @@ -0,0 +1,10 @@ +//! Combinators for rules as defined in a standard. +//! +//! When applicable, these rules have been converted strictly following the ABNF syntax as specified +//! in [RFC 2234]. +//! +//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234 + +pub(crate) mod iso8601; +pub(crate) mod rfc2234; +pub(crate) mod rfc2822; diff --git a/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs b/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs new file mode 100644 index 0000000000..6753444358 --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs @@ -0,0 +1,13 @@ +//! Rules defined in [RFC 2234]. +//! +//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234 + +use crate::parsing::ParsedItem; + +/// Consume exactly one space or tab. +pub(crate) const fn wsp(input: &[u8]) -> Option<ParsedItem<'_, ()>> { + match input { + [b' ' | b'\t', rest @ ..] => Some(ParsedItem(rest, ())), + _ => None, + } +} diff --git a/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs b/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs new file mode 100644 index 0000000000..8410de06e3 --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs @@ -0,0 +1,115 @@ +//! Rules defined in [RFC 2822]. +//! +//! [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822 + +use crate::parsing::combinator::rfc::rfc2234::wsp; +use crate::parsing::combinator::{ascii_char, one_or_more, zero_or_more}; +use crate::parsing::ParsedItem; + +/// Consume the `fws` rule. +// The full rule is equivalent to /\r\n[ \t]+|[ \t]+(?:\r\n[ \t]+)*/ +pub(crate) fn fws(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> { + if let [b'\r', b'\n', rest @ ..] = input { + one_or_more(wsp)(rest) + } else { + input = one_or_more(wsp)(input)?.into_inner(); + while let [b'\r', b'\n', rest @ ..] = input { + input = one_or_more(wsp)(rest)?.into_inner(); + } + Some(ParsedItem(input, ())) + } +} + +/// Consume the `cfws` rule. +// The full rule is equivalent to any combination of `fws` and `comment` so long as it is not empty. +pub(crate) fn cfws(input: &[u8]) -> Option<ParsedItem<'_, ()>> { + one_or_more(|input| fws(input).or_else(|| comment(input)))(input) +} + +/// Consume the `comment` rule. +fn comment(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> { + input = ascii_char::<b'('>(input)?.into_inner(); + input = zero_or_more(fws)(input).into_inner(); + while let Some(rest) = ccontent(input) { + input = rest.into_inner(); + input = zero_or_more(fws)(input).into_inner(); + } + input = ascii_char::<b')'>(input)?.into_inner(); + + Some(ParsedItem(input, ())) +} + +/// Consume the `ccontent` rule. +fn ccontent(input: &[u8]) -> Option<ParsedItem<'_, ()>> { + ctext(input) + .or_else(|| quoted_pair(input)) + .or_else(|| comment(input)) +} + +/// Consume the `ctext` rule. +#[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 +fn ctext(input: &[u8]) -> Option<ParsedItem<'_, ()>> { + no_ws_ctl(input).or_else(|| match input { + [33..=39 | 42..=91 | 93..=126, rest @ ..] => Some(ParsedItem(rest, ())), + _ => None, + }) +} + +/// Consume the `quoted_pair` rule. +fn quoted_pair(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> { + input = ascii_char::<b'\\'>(input)?.into_inner(); + + let old_input_len = input.len(); + + input = text(input).into_inner(); + + // If nothing is parsed, this means we hit the `obs-text` rule and nothing matched. This is + // technically a success, but we should still check the `obs-qp` rule to ensure we consume + // everything possible. + if input.len() == old_input_len { + match input { + [0..=127, rest @ ..] => Some(ParsedItem(rest, ())), + _ => Some(ParsedItem(input, ())), + } + } else { + Some(ParsedItem(input, ())) + } +} + +/// Consume the `no_ws_ctl` rule. +const fn no_ws_ctl(input: &[u8]) -> Option<ParsedItem<'_, ()>> { + match input { + [1..=8 | 11..=12 | 14..=31 | 127, rest @ ..] => Some(ParsedItem(rest, ())), + _ => None, + } +} + +/// Consume the `text` rule. +fn text<'a>(input: &'a [u8]) -> ParsedItem<'a, ()> { + let new_text = |input: &'a [u8]| match input { + [1..=9 | 11..=12 | 14..=127, rest @ ..] => Some(ParsedItem(rest, ())), + _ => None, + }; + + let obs_char = |input: &'a [u8]| match input { + // This is technically allowed, but consuming this would mean the rest of the string is + // eagerly consumed without consideration for where the comment actually ends. + [b')', ..] => None, + [0..=9 | 11..=12 | 14..=127, rest @ ..] => Some(rest), + _ => None, + }; + + let obs_text = |mut input| { + input = zero_or_more(ascii_char::<b'\n'>)(input).into_inner(); + input = zero_or_more(ascii_char::<b'\r'>)(input).into_inner(); + while let Some(rest) = obs_char(input) { + input = rest; + input = zero_or_more(ascii_char::<b'\n'>)(input).into_inner(); + input = zero_or_more(ascii_char::<b'\r'>)(input).into_inner(); + } + + ParsedItem(input, ()) + }; + + new_text(input).unwrap_or_else(|| obs_text(input)) +} diff --git a/third_party/rust/time/src/parsing/component.rs b/third_party/rust/time/src/parsing/component.rs new file mode 100644 index 0000000000..38d632a0da --- /dev/null +++ b/third_party/rust/time/src/parsing/component.rs @@ -0,0 +1,296 @@ +//! Parsing implementations for all [`Component`](crate::format_description::Component)s. + +use core::num::{NonZeroU16, NonZeroU8}; + +use crate::format_description::modifier; +#[cfg(feature = "large-dates")] +use crate::parsing::combinator::n_to_m_digits_padded; +use crate::parsing::combinator::{ + any_digit, exactly_n_digits, exactly_n_digits_padded, first_match, opt, sign, +}; +use crate::parsing::ParsedItem; +use crate::{Month, Weekday}; + +// region: date components +/// Parse the "year" component of a `Date`. +pub(crate) fn parse_year(input: &[u8], modifiers: modifier::Year) -> Option<ParsedItem<'_, i32>> { + match modifiers.repr { + modifier::YearRepr::Full => { + let ParsedItem(input, sign) = opt(sign)(input); + #[cfg(not(feature = "large-dates"))] + let ParsedItem(input, year) = + exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?; + #[cfg(feature = "large-dates")] + let ParsedItem(input, year) = + n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?; + match sign { + Some(b'-') => Some(ParsedItem(input, -(year as i32))), + None if modifiers.sign_is_mandatory || year >= 10_000 => None, + _ => Some(ParsedItem(input, year as i32)), + } + } + modifier::YearRepr::LastTwo => { + Some(exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?.map(|v| v as i32)) + } + } +} + +/// Parse the "month" component of a `Date`. +pub(crate) fn parse_month( + input: &[u8], + modifiers: modifier::Month, +) -> Option<ParsedItem<'_, Month>> { + use Month::*; + let ParsedItem(remaining, value) = first_match( + match modifiers.repr { + modifier::MonthRepr::Numerical => { + return exactly_n_digits_padded::<2, _>(modifiers.padding)(input)? + .flat_map(|n| Month::from_number(n).ok()); + } + modifier::MonthRepr::Long => [ + (b"January".as_slice(), January), + (b"February".as_slice(), February), + (b"March".as_slice(), March), + (b"April".as_slice(), April), + (b"May".as_slice(), May), + (b"June".as_slice(), June), + (b"July".as_slice(), July), + (b"August".as_slice(), August), + (b"September".as_slice(), September), + (b"October".as_slice(), October), + (b"November".as_slice(), November), + (b"December".as_slice(), December), + ], + modifier::MonthRepr::Short => [ + (b"Jan".as_slice(), January), + (b"Feb".as_slice(), February), + (b"Mar".as_slice(), March), + (b"Apr".as_slice(), April), + (b"May".as_slice(), May), + (b"Jun".as_slice(), June), + (b"Jul".as_slice(), July), + (b"Aug".as_slice(), August), + (b"Sep".as_slice(), September), + (b"Oct".as_slice(), October), + (b"Nov".as_slice(), November), + (b"Dec".as_slice(), December), + ], + }, + modifiers.case_sensitive, + )(input)?; + Some(ParsedItem(remaining, value)) +} + +/// Parse the "week number" component of a `Date`. +pub(crate) fn parse_week_number( + input: &[u8], + modifiers: modifier::WeekNumber, +) -> Option<ParsedItem<'_, u8>> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} + +/// Parse the "weekday" component of a `Date`. +pub(crate) fn parse_weekday( + input: &[u8], + modifiers: modifier::Weekday, +) -> Option<ParsedItem<'_, Weekday>> { + first_match( + match (modifiers.repr, modifiers.one_indexed) { + (modifier::WeekdayRepr::Short, _) => [ + (b"Mon".as_slice(), Weekday::Monday), + (b"Tue".as_slice(), Weekday::Tuesday), + (b"Wed".as_slice(), Weekday::Wednesday), + (b"Thu".as_slice(), Weekday::Thursday), + (b"Fri".as_slice(), Weekday::Friday), + (b"Sat".as_slice(), Weekday::Saturday), + (b"Sun".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Long, _) => [ + (b"Monday".as_slice(), Weekday::Monday), + (b"Tuesday".as_slice(), Weekday::Tuesday), + (b"Wednesday".as_slice(), Weekday::Wednesday), + (b"Thursday".as_slice(), Weekday::Thursday), + (b"Friday".as_slice(), Weekday::Friday), + (b"Saturday".as_slice(), Weekday::Saturday), + (b"Sunday".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Sunday, false) => [ + (b"1".as_slice(), Weekday::Monday), + (b"2".as_slice(), Weekday::Tuesday), + (b"3".as_slice(), Weekday::Wednesday), + (b"4".as_slice(), Weekday::Thursday), + (b"5".as_slice(), Weekday::Friday), + (b"6".as_slice(), Weekday::Saturday), + (b"0".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Sunday, true) => [ + (b"2".as_slice(), Weekday::Monday), + (b"3".as_slice(), Weekday::Tuesday), + (b"4".as_slice(), Weekday::Wednesday), + (b"5".as_slice(), Weekday::Thursday), + (b"6".as_slice(), Weekday::Friday), + (b"7".as_slice(), Weekday::Saturday), + (b"1".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Monday, false) => [ + (b"0".as_slice(), Weekday::Monday), + (b"1".as_slice(), Weekday::Tuesday), + (b"2".as_slice(), Weekday::Wednesday), + (b"3".as_slice(), Weekday::Thursday), + (b"4".as_slice(), Weekday::Friday), + (b"5".as_slice(), Weekday::Saturday), + (b"6".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Monday, true) => [ + (b"1".as_slice(), Weekday::Monday), + (b"2".as_slice(), Weekday::Tuesday), + (b"3".as_slice(), Weekday::Wednesday), + (b"4".as_slice(), Weekday::Thursday), + (b"5".as_slice(), Weekday::Friday), + (b"6".as_slice(), Weekday::Saturday), + (b"7".as_slice(), Weekday::Sunday), + ], + }, + modifiers.case_sensitive, + )(input) +} + +/// Parse the "ordinal" component of a `Date`. +pub(crate) fn parse_ordinal( + input: &[u8], + modifiers: modifier::Ordinal, +) -> Option<ParsedItem<'_, NonZeroU16>> { + exactly_n_digits_padded::<3, _>(modifiers.padding)(input) +} + +/// Parse the "day" component of a `Date`. +pub(crate) fn parse_day( + input: &[u8], + modifiers: modifier::Day, +) -> Option<ParsedItem<'_, NonZeroU8>> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} +// endregion date components + +// region: time components +/// Indicate whether the hour is "am" or "pm". +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Period { + #[allow(clippy::missing_docs_in_private_items)] + Am, + #[allow(clippy::missing_docs_in_private_items)] + Pm, +} + +/// Parse the "hour" component of a `Time`. +pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} + +/// Parse the "minute" component of a `Time`. +pub(crate) fn parse_minute( + input: &[u8], + modifiers: modifier::Minute, +) -> Option<ParsedItem<'_, u8>> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} + +/// Parse the "second" component of a `Time`. +pub(crate) fn parse_second( + input: &[u8], + modifiers: modifier::Second, +) -> Option<ParsedItem<'_, u8>> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} + +/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock. +pub(crate) fn parse_period( + input: &[u8], + modifiers: modifier::Period, +) -> Option<ParsedItem<'_, Period>> { + first_match( + if modifiers.is_uppercase { + [ + (b"AM".as_slice(), Period::Am), + (b"PM".as_slice(), Period::Pm), + ] + } else { + [ + (b"am".as_slice(), Period::Am), + (b"pm".as_slice(), Period::Pm), + ] + }, + modifiers.case_sensitive, + )(input) +} + +/// Parse the "subsecond" component of a `Time`. +pub(crate) fn parse_subsecond( + input: &[u8], + modifiers: modifier::Subsecond, +) -> Option<ParsedItem<'_, u32>> { + use modifier::SubsecondDigits::*; + Some(match modifiers.digits { + One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000), + Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000), + Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000), + Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000), + Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000), + Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000), + Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100), + Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10), + Nine => exactly_n_digits::<9, _>(input)?, + OneOrMore => { + let ParsedItem(mut input, mut value) = + any_digit(input)?.map(|v| (v - b'0') as u32 * 100_000_000); + + let mut multiplier = 10_000_000; + while let Some(ParsedItem(new_input, digit)) = any_digit(input) { + value += (digit - b'0') as u32 * multiplier; + input = new_input; + multiplier /= 10; + } + + ParsedItem(input, value) + } + }) +} +// endregion time components + +// region: offset components +/// Parse the "hour" component of a `UtcOffset`. +pub(crate) fn parse_offset_hour( + input: &[u8], + modifiers: modifier::OffsetHour, +) -> Option<ParsedItem<'_, i8>> { + let ParsedItem(input, sign) = opt(sign)(input); + let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?; + match sign { + Some(b'-') => Some(ParsedItem(input, -(hour as i8))), + None if modifiers.sign_is_mandatory => None, + _ => Some(ParsedItem(input, hour as i8)), + } +} + +/// Parse the "minute" component of a `UtcOffset`. +pub(crate) fn parse_offset_minute( + input: &[u8], + modifiers: modifier::OffsetMinute, +) -> Option<ParsedItem<'_, i8>> { + Some( + exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)? + .map(|offset_minute| offset_minute as _), + ) +} + +/// Parse the "second" component of a `UtcOffset`. +pub(crate) fn parse_offset_second( + input: &[u8], + modifiers: modifier::OffsetSecond, +) -> Option<ParsedItem<'_, i8>> { + Some( + exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)? + .map(|offset_second| offset_second as _), + ) +} +// endregion offset components diff --git a/third_party/rust/time/src/parsing/iso8601.rs b/third_party/rust/time/src/parsing/iso8601.rs new file mode 100644 index 0000000000..21c89bab3f --- /dev/null +++ b/third_party/rust/time/src/parsing/iso8601.rs @@ -0,0 +1,308 @@ +//! Parse parts of an ISO 8601-formatted value. + +use crate::error; +use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; +use crate::format_description::well_known::iso8601::EncodedConfig; +use crate::format_description::well_known::Iso8601; +use crate::parsing::combinator::rfc::iso8601::{ + day, dayk, dayo, float, hour, min, month, week, year, ExtendedKind, +}; +use crate::parsing::combinator::{ascii_char, sign}; +use crate::parsing::{Parsed, ParsedItem}; + +impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> { + // Basic: [year][month][day] + // Extended: [year]["-"][month]["-"][day] + // Basic: [year][dayo] + // Extended: [year]["-"][dayo] + // Basic: [year]["W"][week][dayk] + // Extended: [year]["-"]["W"][week]["-"][dayk] + /// Parse a date in the basic or extended format. Reduced precision is permitted. + pub(crate) fn parse_date<'a>( + parsed: &'a mut Parsed, + extended_kind: &'a mut ExtendedKind, + ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a { + move |input| { + // Same for any acceptable format. + let ParsedItem(mut input, year) = year(input).ok_or(InvalidComponent("year"))?; + *extended_kind = match ascii_char::<b'-'>(input) { + Some(ParsedItem(new_input, ())) => { + input = new_input; + ExtendedKind::Extended + } + None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week + }; + + let mut ret_error = match (|| { + let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?; + if extended_kind.is_extended() { + input = ascii_char::<b'-'>(input) + .ok_or(InvalidLiteral)? + .into_inner(); + } + let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?; + Ok(ParsedItem(input, (month, day))) + })() { + Ok(ParsedItem(input, (month, day))) => { + *parsed = parsed + .with_year(year) + .ok_or(InvalidComponent("year"))? + .with_month(month) + .ok_or(InvalidComponent("month"))? + .with_day(day) + .ok_or(InvalidComponent("day"))?; + return Ok(input); + } + Err(err) => err, + }; + + // Don't check for `None`, as the error from year-month-day will always take priority. + if let Some(ParsedItem(input, ordinal)) = dayo(input) { + *parsed = parsed + .with_year(year) + .ok_or(InvalidComponent("year"))? + .with_ordinal(ordinal) + .ok_or(InvalidComponent("ordinal"))?; + return Ok(input); + } + + match (|| { + let input = ascii_char::<b'W'>(input) + .ok_or((false, InvalidLiteral))? + .into_inner(); + let ParsedItem(mut input, week) = + week(input).ok_or((true, InvalidComponent("week")))?; + if extended_kind.is_extended() { + input = ascii_char::<b'-'>(input) + .ok_or((true, InvalidLiteral))? + .into_inner(); + } + let ParsedItem(input, weekday) = + dayk(input).ok_or((true, InvalidComponent("weekday")))?; + Ok(ParsedItem(input, (week, weekday))) + })() { + Ok(ParsedItem(input, (week, weekday))) => { + *parsed = parsed + .with_iso_year(year) + .ok_or(InvalidComponent("year"))? + .with_iso_week_number(week) + .ok_or(InvalidComponent("week"))? + .with_weekday(weekday) + .ok_or(InvalidComponent("weekday"))?; + return Ok(input); + } + Err((false, _err)) => {} + // This error is more accurate than the one from year-month-day. + Err((true, err)) => ret_error = err, + } + + Err(ret_error.into()) + } + } + + // Basic: ["T"][hour][min][sec] + // Extended: ["T"][hour][":"][min][":"][sec] + // Reduced precision: components after [hour] (including their preceding separator) can be + // omitted. ["T"] can be omitted if there is no date present. + /// Parse a time in the basic or extended format. Reduced precision is permitted. + pub(crate) fn parse_time<'a>( + parsed: &'a mut Parsed, + extended_kind: &'a mut ExtendedKind, + date_is_present: bool, + ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a { + move |mut input| { + if date_is_present { + input = ascii_char::<b'T'>(input) + .ok_or(InvalidLiteral)? + .into_inner(); + } + + let ParsedItem(mut input, hour) = float(input).ok_or(InvalidComponent("hour"))?; + match hour { + (hour, None) => parsed.set_hour_24(hour).ok_or(InvalidComponent("hour"))?, + (hour, Some(fractional_part)) => { + *parsed = parsed + .with_hour_24(hour) + .ok_or(InvalidComponent("hour"))? + .with_minute((fractional_part * 60.0) as _) + .ok_or(InvalidComponent("minute"))? + .with_second((fractional_part * 3600.0 % 60.) as _) + .ok_or(InvalidComponent("second"))? + .with_subsecond( + (fractional_part * 3_600. * 1_000_000_000. % 1_000_000_000.) as _, + ) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + }; + + if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) { + extended_kind + .coerce_extended() + .ok_or(InvalidComponent("minute"))?; + input = new_input; + }; + + let mut input = match float(input) { + Some(ParsedItem(input, (minute, None))) => { + extended_kind.coerce_basic(); + parsed + .set_minute(minute) + .ok_or(InvalidComponent("minute"))?; + input + } + Some(ParsedItem(input, (minute, Some(fractional_part)))) => { + // `None` is valid behavior, so don't error if this fails. + extended_kind.coerce_basic(); + *parsed = parsed + .with_minute(minute) + .ok_or(InvalidComponent("minute"))? + .with_second((fractional_part * 60.) as _) + .ok_or(InvalidComponent("second"))? + .with_subsecond( + (fractional_part * 60. * 1_000_000_000. % 1_000_000_000.) as _, + ) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + // colon was present, so minutes are required + None if extended_kind.is_extended() => { + return Err(error::Parse::ParseFromDescription(InvalidComponent( + "minute", + ))); + } + None => { + // Missing components are assumed to be zero. + *parsed = parsed + .with_minute(0) + .ok_or(InvalidComponent("minute"))? + .with_second(0) + .ok_or(InvalidComponent("second"))? + .with_subsecond(0) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + }; + + if extended_kind.is_extended() { + match ascii_char::<b':'>(input) { + Some(ParsedItem(new_input, ())) => input = new_input, + None => { + *parsed = parsed + .with_second(0) + .ok_or(InvalidComponent("second"))? + .with_subsecond(0) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + } + } + + let (input, second, subsecond) = match float(input) { + Some(ParsedItem(input, (second, None))) => (input, second, 0), + Some(ParsedItem(input, (second, Some(fractional_part)))) => { + (input, second, round(fractional_part * 1_000_000_000.) as _) + } + None if extended_kind.is_extended() => { + return Err(error::Parse::ParseFromDescription(InvalidComponent( + "second", + ))); + } + // Missing components are assumed to be zero. + None => (input, 0, 0), + }; + *parsed = parsed + .with_second(second) + .ok_or(InvalidComponent("second"))? + .with_subsecond(subsecond) + .ok_or(InvalidComponent("subsecond"))?; + + Ok(input) + } + } + + // Basic: [±][hour][min] or ["Z"] + // Extended: [±][hour][":"][min] or ["Z"] + // Reduced precision: [±][hour] or ["Z"] + /// Parse a UTC offset in the basic or extended format. Reduced precision is supported. + pub(crate) fn parse_offset<'a>( + parsed: &'a mut Parsed, + extended_kind: &'a mut ExtendedKind, + ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a { + move |input| { + if let Some(ParsedItem(input, ())) = ascii_char::<b'Z'>(input) { + *parsed = parsed + .with_offset_hour(0) + .ok_or(InvalidComponent("offset hour"))? + .with_offset_minute_signed(0) + .ok_or(InvalidComponent("offset minute"))? + .with_offset_second_signed(0) + .ok_or(InvalidComponent("offset second"))?; + return Ok(input); + } + + let ParsedItem(input, sign) = sign(input).ok_or(InvalidComponent("offset hour"))?; + let mut input = hour(input) + .and_then(|parsed_item| { + parsed_item.consume_value(|hour| { + parsed.set_offset_hour(if sign == b'-' { + -(hour as i8) + } else { + hour as _ + }) + }) + }) + .ok_or(InvalidComponent("offset hour"))?; + + if extended_kind.maybe_extended() { + if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) { + extended_kind + .coerce_extended() + .ok_or(InvalidComponent("offset minute"))?; + input = new_input; + }; + } + + let input = min(input) + .and_then(|parsed_item| { + parsed_item.consume_value(|min| { + parsed.set_offset_minute_signed(if sign == b'-' { + -(min as i8) + } else { + min as _ + }) + }) + }) + .ok_or(InvalidComponent("offset minute"))?; + // If `:` was present, the format has already been set to extended. As such, this call + // will do nothing in that case. If there wasn't `:` but minutes were + // present, we know it's the basic format. Do not use `?` on the call, as + // returning `None` is valid behavior. + extended_kind.coerce_basic(); + + Ok(input) + } + } +} + +/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual +/// implementation for `no_std` +fn round(value: f64) -> f64 { + #[cfg(feature = "std")] + { + value.round() + } + #[cfg(not(feature = "std"))] + { + round_impl(value) + } +} + +#[cfg(not(feature = "std"))] +#[allow(clippy::missing_docs_in_private_items)] +fn round_impl(value: f64) -> f64 { + debug_assert!(value.is_sign_positive() && !value.is_nan()); + + let f = value % 1.; + if f < 0.5 { value - f } else { value - f + 1. } +} diff --git a/third_party/rust/time/src/parsing/mod.rs b/third_party/rust/time/src/parsing/mod.rs new file mode 100644 index 0000000000..4a2aa829f1 --- /dev/null +++ b/third_party/rust/time/src/parsing/mod.rs @@ -0,0 +1,50 @@ +//! Parsing for various types. + +pub(crate) mod combinator; +pub(crate) mod component; +mod iso8601; +pub(crate) mod parsable; +mod parsed; +pub(crate) mod shim; + +pub use self::parsable::Parsable; +pub use self::parsed::Parsed; + +/// An item that has been parsed. Represented as a `(remaining, value)` pair. +#[derive(Debug)] +pub(crate) struct ParsedItem<'a, T>(pub(crate) &'a [u8], pub(crate) T); + +impl<'a, T> ParsedItem<'a, T> { + /// Map the value to a new value, preserving the remaining input. + pub(crate) fn map<U>(self, f: impl FnOnce(T) -> U) -> ParsedItem<'a, U> { + ParsedItem(self.0, f(self.1)) + } + + /// Map the value to a new, optional value, preserving the remaining input. + pub(crate) fn flat_map<U>(self, f: impl FnOnce(T) -> Option<U>) -> Option<ParsedItem<'a, U>> { + Some(ParsedItem(self.0, f(self.1)?)) + } + + /// Consume the stored value with the provided function. The remaining input is returned. + #[must_use = "this returns the remaining input"] + pub(crate) fn consume_value(self, f: impl FnOnce(T) -> Option<()>) -> Option<&'a [u8]> { + f(self.1)?; + Some(self.0) + } +} + +impl<'a> ParsedItem<'a, ()> { + /// Discard the unit value, returning the remaining input. + #[must_use = "this returns the remaining input"] + pub(crate) const fn into_inner(self) -> &'a [u8] { + self.0 + } +} + +impl<'a> ParsedItem<'a, Option<()>> { + /// Discard the potential unit value, returning the remaining input. + #[must_use = "this returns the remaining input"] + pub(crate) const fn into_inner(self) -> &'a [u8] { + self.0 + } +} diff --git a/third_party/rust/time/src/parsing/parsable.rs b/third_party/rust/time/src/parsing/parsable.rs new file mode 100644 index 0000000000..badb638088 --- /dev/null +++ b/third_party/rust/time/src/parsing/parsable.rs @@ -0,0 +1,754 @@ +//! A trait that can be used to parse an item from an input. + +use core::ops::Deref; + +use crate::error::TryFromParsed; +use crate::format_description::well_known::iso8601::EncodedConfig; +use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339}; +use crate::format_description::FormatItem; +#[cfg(feature = "alloc")] +use crate::format_description::OwnedFormatItem; +use crate::parsing::{Parsed, ParsedItem}; +use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; + +/// A type that can be parsed. +#[cfg_attr(__time_03_docs, doc(notable_trait))] +pub trait Parsable: sealed::Sealed {} +impl Parsable for FormatItem<'_> {} +impl Parsable for [FormatItem<'_>] {} +#[cfg(feature = "alloc")] +impl Parsable for OwnedFormatItem {} +#[cfg(feature = "alloc")] +impl Parsable for [OwnedFormatItem] {} +impl Parsable for Rfc2822 {} +impl Parsable for Rfc3339 {} +impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {} +impl<T: Deref> Parsable for T where T::Target: Parsable {} + +/// Seal the trait to prevent downstream users from implementing it, while still allowing it to +/// exist in generic bounds. +mod sealed { + + #[allow(clippy::wildcard_imports)] + use super::*; + + /// Parse the item using a format description and an input. + pub trait Sealed { + /// Parse the item into the provided [`Parsed`] struct. + /// + /// This method can be used to parse a single component without parsing the full value. + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse>; + + /// Parse the item into a new [`Parsed`] struct. + /// + /// This method can only be used to parse a complete value of a type. If any characters + /// remain after parsing, an error will be returned. + fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> { + let mut parsed = Parsed::new(); + if self.parse_into(input, &mut parsed)?.is_empty() { + Ok(parsed) + } else { + Err(error::Parse::UnexpectedTrailingCharacters) + } + } + + /// Parse a [`Date`] from the format description. + fn parse_date(&self, input: &[u8]) -> Result<Date, error::Parse> { + Ok(self.parse(input)?.try_into()?) + } + + /// Parse a [`Time`] from the format description. + fn parse_time(&self, input: &[u8]) -> Result<Time, error::Parse> { + Ok(self.parse(input)?.try_into()?) + } + + /// Parse a [`UtcOffset`] from the format description. + fn parse_offset(&self, input: &[u8]) -> Result<UtcOffset, error::Parse> { + Ok(self.parse(input)?.try_into()?) + } + + /// Parse a [`PrimitiveDateTime`] from the format description. + fn parse_date_time(&self, input: &[u8]) -> Result<PrimitiveDateTime, error::Parse> { + Ok(self.parse(input)?.try_into()?) + } + + /// Parse a [`OffsetDateTime`] from the format description. + fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> { + Ok(self.parse(input)?.try_into()?) + } + } +} + +// region: custom formats +impl sealed::Sealed for FormatItem<'_> { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + Ok(parsed.parse_item(input, self)?) + } +} + +impl sealed::Sealed for [FormatItem<'_>] { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + Ok(parsed.parse_items(input, self)?) + } +} + +#[cfg(feature = "alloc")] +impl sealed::Sealed for OwnedFormatItem { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + Ok(parsed.parse_item(input, self)?) + } +} + +#[cfg(feature = "alloc")] +impl sealed::Sealed for [OwnedFormatItem] { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + Ok(parsed.parse_items(input, self)?) + } +} + +impl<T: Deref> sealed::Sealed for T +where + T::Target: sealed::Sealed, +{ + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + self.deref().parse_into(input, parsed) + } +} +// endregion custom formats + +// region: well-known formats +impl sealed::Sealed for Rfc2822 { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; + use crate::parsing::combinator::rfc::rfc2822::{cfws, fws}; + use crate::parsing::combinator::{ + ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign, + }; + + let colon = ascii_char::<b':'>; + let comma = ascii_char::<b','>; + + let input = opt(fws)(input).into_inner(); + let input = first_match( + [ + (b"Mon".as_slice(), Weekday::Monday), + (b"Tue".as_slice(), Weekday::Tuesday), + (b"Wed".as_slice(), Weekday::Wednesday), + (b"Thu".as_slice(), Weekday::Thursday), + (b"Fri".as_slice(), Weekday::Friday), + (b"Sat".as_slice(), Weekday::Saturday), + (b"Sun".as_slice(), Weekday::Sunday), + ], + false, + )(input) + .and_then(|item| item.consume_value(|value| parsed.set_weekday(value))) + .ok_or(InvalidComponent("weekday"))?; + let input = comma(input).ok_or(InvalidLiteral)?.into_inner(); + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let input = n_to_m_digits::<1, 2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_day(value))) + .ok_or(InvalidComponent("day"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let input = first_match( + [ + (b"Jan".as_slice(), Month::January), + (b"Feb".as_slice(), Month::February), + (b"Mar".as_slice(), Month::March), + (b"Apr".as_slice(), Month::April), + (b"May".as_slice(), Month::May), + (b"Jun".as_slice(), Month::June), + (b"Jul".as_slice(), Month::July), + (b"Aug".as_slice(), Month::August), + (b"Sep".as_slice(), Month::September), + (b"Oct".as_slice(), Month::October), + (b"Nov".as_slice(), Month::November), + (b"Dec".as_slice(), Month::December), + ], + false, + )(input) + .and_then(|item| item.consume_value(|value| parsed.set_month(value))) + .ok_or(InvalidComponent("month"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let input = match exactly_n_digits::<4, u32>(input) { + Some(item) => { + let input = item + .flat_map(|year| if year >= 1900 { Some(year) } else { None }) + .and_then(|item| item.consume_value(|value| parsed.set_year(value as _))) + .ok_or(InvalidComponent("year"))?; + fws(input).ok_or(InvalidLiteral)?.into_inner() + } + None => { + let input = exactly_n_digits::<2, u32>(input) + .and_then(|item| { + item.map(|year| if year < 50 { year + 2000 } else { year + 1900 }) + .map(|year| year as _) + .consume_value(|value| parsed.set_year(value)) + }) + .ok_or(InvalidComponent("year"))?; + cfws(input).ok_or(InvalidLiteral)?.into_inner() + } + }; + + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value))) + .ok_or(InvalidComponent("hour"))?; + let input = opt(cfws)(input).into_inner(); + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = opt(cfws)(input).into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_minute(value))) + .ok_or(InvalidComponent("minute"))?; + + let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) { + let input = input.into_inner(); // discard the colon + let input = opt(cfws)(input).into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_second(value))) + .ok_or(InvalidComponent("second"))?; + cfws(input).ok_or(InvalidLiteral)?.into_inner() + } else { + cfws(input).ok_or(InvalidLiteral)?.into_inner() + }; + + // The RFC explicitly allows leap seconds. + parsed.set_leap_second_allowed(true); + + #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 + let zone_literal = first_match( + [ + (b"UT".as_slice(), 0), + (b"GMT".as_slice(), 0), + (b"EST".as_slice(), -5), + (b"EDT".as_slice(), -4), + (b"CST".as_slice(), -6), + (b"CDT".as_slice(), -5), + (b"MST".as_slice(), -7), + (b"MDT".as_slice(), -6), + (b"PST".as_slice(), -8), + (b"PDT".as_slice(), -7), + ], + false, + )(input) + .or_else(|| match input { + [ + b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z', + rest @ .., + ] => Some(ParsedItem(rest, 0)), + _ => None, + }); + if let Some(zone_literal) = zone_literal { + let input = zone_literal + .consume_value(|value| parsed.set_offset_hour(value)) + .ok_or(InvalidComponent("offset hour"))?; + parsed + .set_offset_minute_signed(0) + .ok_or(InvalidComponent("offset minute"))?; + parsed + .set_offset_second_signed(0) + .ok_or(InvalidComponent("offset second"))?; + return Ok(input); + } + + let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?; + let input = exactly_n_digits::<2, u8>(input) + .and_then(|item| { + item.map(|offset_hour| { + if offset_sign == b'-' { + -(offset_hour as i8) + } else { + offset_hour as _ + } + }) + .consume_value(|value| parsed.set_offset_hour(value)) + }) + .ok_or(InvalidComponent("offset hour"))?; + let input = exactly_n_digits::<2, u8>(input) + .and_then(|item| { + item.consume_value(|value| parsed.set_offset_minute_signed(value as _)) + }) + .ok_or(InvalidComponent("offset minute"))?; + + Ok(input) + } + + fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> { + use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; + use crate::parsing::combinator::rfc::rfc2822::{cfws, fws}; + use crate::parsing::combinator::{ + ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign, + }; + + let colon = ascii_char::<b':'>; + let comma = ascii_char::<b','>; + + let input = opt(fws)(input).into_inner(); + // This parses the weekday, but we don't actually use the value anywhere. Because of this, + // just return `()` to avoid unnecessary generated code. + let ParsedItem(input, ()) = first_match( + [ + (b"Mon".as_slice(), ()), + (b"Tue".as_slice(), ()), + (b"Wed".as_slice(), ()), + (b"Thu".as_slice(), ()), + (b"Fri".as_slice(), ()), + (b"Sat".as_slice(), ()), + (b"Sun".as_slice(), ()), + ], + false, + )(input) + .ok_or(InvalidComponent("weekday"))?; + let input = comma(input).ok_or(InvalidLiteral)?.into_inner(); + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, day) = + n_to_m_digits::<1, 2, _>(input).ok_or(InvalidComponent("day"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, month) = first_match( + [ + (b"Jan".as_slice(), Month::January), + (b"Feb".as_slice(), Month::February), + (b"Mar".as_slice(), Month::March), + (b"Apr".as_slice(), Month::April), + (b"May".as_slice(), Month::May), + (b"Jun".as_slice(), Month::June), + (b"Jul".as_slice(), Month::July), + (b"Aug".as_slice(), Month::August), + (b"Sep".as_slice(), Month::September), + (b"Oct".as_slice(), Month::October), + (b"Nov".as_slice(), Month::November), + (b"Dec".as_slice(), Month::December), + ], + false, + )(input) + .ok_or(InvalidComponent("month"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let (input, year) = match exactly_n_digits::<4, u32>(input) { + Some(item) => { + let ParsedItem(input, year) = item + .flat_map(|year| if year >= 1900 { Some(year) } else { None }) + .ok_or(InvalidComponent("year"))?; + let input = fws(input).ok_or(InvalidLiteral)?.into_inner(); + (input, year) + } + None => { + let ParsedItem(input, year) = exactly_n_digits::<2, u32>(input) + .map(|item| item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })) + .ok_or(InvalidComponent("year"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + (input, year) + } + }; + + let ParsedItem(input, hour) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?; + let input = opt(cfws)(input).into_inner(); + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = opt(cfws)(input).into_inner(); + let ParsedItem(input, minute) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?; + + let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) { + let input = input.into_inner(); // discard the colon + let input = opt(cfws)(input).into_inner(); + let ParsedItem(input, second) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + (input, second) + } else { + (cfws(input).ok_or(InvalidLiteral)?.into_inner(), 0) + }; + + #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 + let zone_literal = first_match( + [ + (b"UT".as_slice(), 0), + (b"GMT".as_slice(), 0), + (b"EST".as_slice(), -5), + (b"EDT".as_slice(), -4), + (b"CST".as_slice(), -6), + (b"CDT".as_slice(), -5), + (b"MST".as_slice(), -7), + (b"MDT".as_slice(), -6), + (b"PST".as_slice(), -8), + (b"PDT".as_slice(), -7), + ], + false, + )(input) + .or_else(|| match input { + [ + b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z', + rest @ .., + ] => Some(ParsedItem(rest, 0)), + _ => None, + }); + + let (input, offset_hour, offset_minute) = if let Some(zone_literal) = zone_literal { + let ParsedItem(input, offset_hour) = zone_literal; + (input, offset_hour, 0) + } else { + let ParsedItem(input, offset_sign) = + sign(input).ok_or(InvalidComponent("offset hour"))?; + let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input) + .map(|item| { + item.map(|offset_hour| { + if offset_sign == b'-' { + -(offset_hour as i8) + } else { + offset_hour as _ + } + }) + }) + .ok_or(InvalidComponent("offset hour"))?; + let ParsedItem(input, offset_minute) = + exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?; + (input, offset_hour, offset_minute as i8) + }; + + if !input.is_empty() { + return Err(error::Parse::UnexpectedTrailingCharacters); + } + + let mut nanosecond = 0; + let leap_second_input = if second == 60 { + second = 59; + nanosecond = 999_999_999; + true + } else { + false + }; + + let dt = (|| { + let date = Date::from_calendar_date(year as _, month, day)?; + let time = Time::from_hms_nano(hour, minute, second, nanosecond)?; + let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?; + Ok(date.with_time(time).assume_offset(offset)) + })() + .map_err(TryFromParsed::ComponentRange)?; + + if leap_second_input && !dt.is_valid_leap_second_stand_in() { + return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange( + error::ComponentRange { + name: "second", + minimum: 0, + maximum: 59, + value: 60, + conditional_range: true, + }, + ))); + } + + Ok(dt) + } +} + +impl sealed::Sealed for Rfc3339 { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; + use crate::parsing::combinator::{ + any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign, + }; + + let dash = ascii_char::<b'-'>; + let colon = ascii_char::<b':'>; + + let input = exactly_n_digits::<4, u32>(input) + .and_then(|item| item.consume_value(|value| parsed.set_year(value as _))) + .ok_or(InvalidComponent("year"))?; + let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.flat_map(|value| Month::from_number(value).ok())) + .and_then(|item| item.consume_value(|value| parsed.set_month(value))) + .ok_or(InvalidComponent("month"))?; + let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_day(value))) + .ok_or(InvalidComponent("day"))?; + let input = ascii_char_ignore_case::<b'T'>(input) + .ok_or(InvalidLiteral)? + .into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value))) + .ok_or(InvalidComponent("hour"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_minute(value))) + .ok_or(InvalidComponent("minute"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_second(value))) + .ok_or(InvalidComponent("second"))?; + let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) { + let ParsedItem(mut input, mut value) = any_digit(input) + .ok_or(InvalidComponent("subsecond"))? + .map(|v| (v - b'0') as u32 * 100_000_000); + + let mut multiplier = 10_000_000; + while let Some(ParsedItem(new_input, digit)) = any_digit(input) { + value += (digit - b'0') as u32 * multiplier; + input = new_input; + multiplier /= 10; + } + + parsed + .set_subsecond(value) + .ok_or(InvalidComponent("subsecond"))?; + input + } else { + input + }; + + // The RFC explicitly allows leap seconds. + parsed.set_leap_second_allowed(true); + + if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) { + parsed + .set_offset_hour(0) + .ok_or(InvalidComponent("offset hour"))?; + parsed + .set_offset_minute_signed(0) + .ok_or(InvalidComponent("offset minute"))?; + parsed + .set_offset_second_signed(0) + .ok_or(InvalidComponent("offset second"))?; + return Ok(input); + } + + let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?; + let input = exactly_n_digits::<2, u8>(input) + .and_then(|item| { + item.map(|offset_hour| { + if offset_sign == b'-' { + -(offset_hour as i8) + } else { + offset_hour as _ + } + }) + .consume_value(|value| parsed.set_offset_hour(value)) + }) + .ok_or(InvalidComponent("offset hour"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, u8>(input) + .and_then(|item| { + item.map(|offset_minute| { + if offset_sign == b'-' { + -(offset_minute as i8) + } else { + offset_minute as _ + } + }) + .consume_value(|value| parsed.set_offset_minute_signed(value)) + }) + .ok_or(InvalidComponent("offset minute"))?; + + Ok(input) + } + + fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> { + use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; + use crate::parsing::combinator::{ + any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign, + }; + + let dash = ascii_char::<b'-'>; + let colon = ascii_char::<b':'>; + + let ParsedItem(input, year) = + exactly_n_digits::<4, u32>(input).ok_or(InvalidComponent("year"))?; + let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, month) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("month"))?; + let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, day) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("day"))?; + let input = ascii_char_ignore_case::<b'T'>(input) + .ok_or(InvalidLiteral)? + .into_inner(); + let ParsedItem(input, hour) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, minute) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, mut second) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?; + let ParsedItem(input, mut nanosecond) = + if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) { + let ParsedItem(mut input, mut value) = any_digit(input) + .ok_or(InvalidComponent("subsecond"))? + .map(|v| (v - b'0') as u32 * 100_000_000); + + let mut multiplier = 10_000_000; + while let Some(ParsedItem(new_input, digit)) = any_digit(input) { + value += (digit - b'0') as u32 * multiplier; + input = new_input; + multiplier /= 10; + } + + ParsedItem(input, value) + } else { + ParsedItem(input, 0) + }; + let ParsedItem(input, offset) = { + if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) { + ParsedItem(input, UtcOffset::UTC) + } else { + let ParsedItem(input, offset_sign) = + sign(input).ok_or(InvalidComponent("offset hour"))?; + let ParsedItem(input, offset_hour) = + exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset hour"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, offset_minute) = + exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?; + UtcOffset::from_hms( + if offset_sign == b'-' { + -(offset_hour as i8) + } else { + offset_hour as _ + }, + if offset_sign == b'-' { + -(offset_minute as i8) + } else { + offset_minute as _ + }, + 0, + ) + .map(|offset| ParsedItem(input, offset)) + .map_err(|mut err| { + // Provide the user a more accurate error. + if err.name == "hours" { + err.name = "offset hour"; + } else if err.name == "minutes" { + err.name = "offset minute"; + } + err + }) + .map_err(TryFromParsed::ComponentRange)? + } + }; + + if !input.is_empty() { + return Err(error::Parse::UnexpectedTrailingCharacters); + } + + // The RFC explicitly permits leap seconds. We don't currently support them, so treat it as + // the preceding nanosecond. However, leap seconds can only occur as the last second of the + // month UTC. + let leap_second_input = if second == 60 { + second = 59; + nanosecond = 999_999_999; + true + } else { + false + }; + + let dt = Month::from_number(month) + .and_then(|month| Date::from_calendar_date(year as _, month, day)) + .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond)) + .map(|date| date.assume_offset(offset)) + .map_err(TryFromParsed::ComponentRange)?; + + if leap_second_input && !dt.is_valid_leap_second_stand_in() { + return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange( + error::ComponentRange { + name: "second", + minimum: 0, + maximum: 59, + value: 60, + conditional_range: true, + }, + ))); + } + + Ok(dt) + } +} + +impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> { + fn parse_into<'a>( + &self, + mut input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + use crate::parsing::combinator::rfc::iso8601::ExtendedKind; + + let mut extended_kind = ExtendedKind::Unknown; + let mut date_is_present = false; + let mut time_is_present = false; + let mut offset_is_present = false; + let mut first_error = None; + + match Self::parse_date(parsed, &mut extended_kind)(input) { + Ok(new_input) => { + input = new_input; + date_is_present = true; + } + Err(err) => { + first_error.get_or_insert(err); + } + } + + match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) { + Ok(new_input) => { + input = new_input; + time_is_present = true; + } + Err(err) => { + first_error.get_or_insert(err); + } + } + + // If a date and offset are present, a time must be as well. + if !date_is_present || time_is_present { + match Self::parse_offset(parsed, &mut extended_kind)(input) { + Ok(new_input) => { + input = new_input; + offset_is_present = true; + } + Err(err) => { + first_error.get_or_insert(err); + } + } + } + + if !date_is_present && !time_is_present && !offset_is_present { + match first_error { + Some(err) => return Err(err), + None => unreachable!("an error should be present if no components were parsed"), + } + } + + Ok(input) + } +} +// endregion well-known formats diff --git a/third_party/rust/time/src/parsing/parsed.rs b/third_party/rust/time/src/parsing/parsed.rs new file mode 100644 index 0000000000..7b2279cbb1 --- /dev/null +++ b/third_party/rust/time/src/parsing/parsed.rs @@ -0,0 +1,759 @@ +//! Information parsed from an input and format description. + +use core::mem::MaybeUninit; +use core::num::{NonZeroU16, NonZeroU8}; + +use crate::error::TryFromParsed::InsufficientInformation; +use crate::format_description::modifier::{WeekNumberRepr, YearRepr}; +#[cfg(feature = "alloc")] +use crate::format_description::OwnedFormatItem; +use crate::format_description::{Component, FormatItem}; +use crate::parsing::component::{ + parse_day, parse_hour, parse_minute, parse_month, parse_offset_hour, parse_offset_minute, + parse_offset_second, parse_ordinal, parse_period, parse_second, parse_subsecond, + parse_week_number, parse_weekday, parse_year, Period, +}; +use crate::parsing::ParsedItem; +use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; + +/// Sealed to prevent downstream implementations. +mod sealed { + use super::*; + + /// A trait to allow `parse_item` to be generic. + pub trait AnyFormatItem { + /// Parse a single item, returning the remaining input on success. + fn parse_item<'a>( + &self, + parsed: &mut Parsed, + input: &'a [u8], + ) -> Result<&'a [u8], error::ParseFromDescription>; + } +} + +impl sealed::AnyFormatItem for FormatItem<'_> { + fn parse_item<'a>( + &self, + parsed: &mut Parsed, + input: &'a [u8], + ) -> Result<&'a [u8], error::ParseFromDescription> { + match self { + Self::Literal(literal) => Parsed::parse_literal(input, literal), + Self::Component(component) => parsed.parse_component(input, *component), + Self::Compound(compound) => parsed.parse_items(input, compound), + Self::Optional(item) => parsed.parse_item(input, *item).or(Ok(input)), + Self::First(items) => { + let mut first_err = None; + + for item in items.iter() { + match parsed.parse_item(input, item) { + Ok(remaining_input) => return Ok(remaining_input), + Err(err) if first_err.is_none() => first_err = Some(err), + Err(_) => {} + } + } + + match first_err { + Some(err) => Err(err), + // This location will be reached if the slice is empty, skipping the `for` loop. + // As this case is expected to be uncommon, there's no need to check up front. + None => Ok(input), + } + } + } + } +} + +#[cfg(feature = "alloc")] +impl sealed::AnyFormatItem for OwnedFormatItem { + fn parse_item<'a>( + &self, + parsed: &mut Parsed, + input: &'a [u8], + ) -> Result<&'a [u8], error::ParseFromDescription> { + match self { + Self::Literal(literal) => Parsed::parse_literal(input, literal), + Self::Component(component) => parsed.parse_component(input, *component), + Self::Compound(compound) => parsed.parse_items(input, compound), + Self::Optional(item) => parsed.parse_item(input, item.as_ref()).or(Ok(input)), + Self::First(items) => { + let mut first_err = None; + + for item in items.iter() { + match parsed.parse_item(input, item) { + Ok(remaining_input) => return Ok(remaining_input), + Err(err) if first_err.is_none() => first_err = Some(err), + Err(_) => {} + } + } + + match first_err { + Some(err) => Err(err), + // This location will be reached if the slice is empty, skipping the `for` loop. + // As this case is expected to be uncommon, there's no need to check up front. + None => Ok(input), + } + } + } + } +} + +/// All information parsed. +/// +/// This information is directly used to construct the final values. +/// +/// Most users will not need think about this struct in any way. It is public to allow for manual +/// control over values, in the instance that the default parser is insufficient. +#[derive(Debug, Clone, Copy)] +pub struct Parsed { + /// Bitflags indicating whether a particular field is present. + flags: u16, + /// Calendar year. + year: MaybeUninit<i32>, + /// The last two digits of the calendar year. + year_last_two: MaybeUninit<u8>, + /// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date). + iso_year: MaybeUninit<i32>, + /// The last two digits of the ISO week year. + iso_year_last_two: MaybeUninit<u8>, + /// Month of the year. + month: Option<Month>, + /// Week of the year, where week one begins on the first Sunday of the calendar year. + sunday_week_number: MaybeUninit<u8>, + /// Week of the year, where week one begins on the first Monday of the calendar year. + monday_week_number: MaybeUninit<u8>, + /// Week of the year, where week one is the Monday-to-Sunday period containing January 4. + iso_week_number: Option<NonZeroU8>, + /// Day of the week. + weekday: Option<Weekday>, + /// Day of the year. + ordinal: Option<NonZeroU16>, + /// Day of the month. + day: Option<NonZeroU8>, + /// Hour within the day. + hour_24: MaybeUninit<u8>, + /// Hour within the 12-hour period (midnight to noon or vice versa). This is typically used in + /// conjunction with AM/PM, which is indicated by the `hour_12_is_pm` field. + hour_12: Option<NonZeroU8>, + /// Whether the `hour_12` field indicates a time that "PM". + hour_12_is_pm: Option<bool>, + /// Minute within the hour. + minute: MaybeUninit<u8>, + /// Second within the minute. + second: MaybeUninit<u8>, + /// Nanosecond within the second. + subsecond: MaybeUninit<u32>, + /// Whole hours of the UTC offset. + offset_hour: MaybeUninit<i8>, + /// Minutes within the hour of the UTC offset. + offset_minute: MaybeUninit<i8>, + /// Seconds within the minute of the UTC offset. + offset_second: MaybeUninit<i8>, +} + +#[allow(clippy::missing_docs_in_private_items)] +impl Parsed { + const YEAR_FLAG: u16 = 1 << 0; + const YEAR_LAST_TWO_FLAG: u16 = 1 << 1; + const ISO_YEAR_FLAG: u16 = 1 << 2; + const ISO_YEAR_LAST_TWO_FLAG: u16 = 1 << 3; + const SUNDAY_WEEK_NUMBER_FLAG: u16 = 1 << 4; + const MONDAY_WEEK_NUMBER_FLAG: u16 = 1 << 5; + const HOUR_24_FLAG: u16 = 1 << 6; + const MINUTE_FLAG: u16 = 1 << 7; + const SECOND_FLAG: u16 = 1 << 8; + const SUBSECOND_FLAG: u16 = 1 << 9; + const OFFSET_HOUR_FLAG: u16 = 1 << 10; + const OFFSET_MINUTE_FLAG: u16 = 1 << 11; + const OFFSET_SECOND_FLAG: u16 = 1 << 12; + /// Indicates whether a leap second is permitted to be parsed. This is required by some + /// well-known formats. + const LEAP_SECOND_ALLOWED_FLAG: u16 = 1 << 13; +} + +impl Parsed { + /// Create a new instance of `Parsed` with no information known. + pub const fn new() -> Self { + Self { + flags: 0, + year: MaybeUninit::uninit(), + year_last_two: MaybeUninit::uninit(), + iso_year: MaybeUninit::uninit(), + iso_year_last_two: MaybeUninit::uninit(), + month: None, + sunday_week_number: MaybeUninit::uninit(), + monday_week_number: MaybeUninit::uninit(), + iso_week_number: None, + weekday: None, + ordinal: None, + day: None, + hour_24: MaybeUninit::uninit(), + hour_12: None, + hour_12_is_pm: None, + minute: MaybeUninit::uninit(), + second: MaybeUninit::uninit(), + subsecond: MaybeUninit::uninit(), + offset_hour: MaybeUninit::uninit(), + offset_minute: MaybeUninit::uninit(), + offset_second: MaybeUninit::uninit(), + } + } + + /// Parse a single [`FormatItem`] or [`OwnedFormatItem`], mutating the struct. The remaining + /// input is returned as the `Ok` value. + /// + /// If a [`FormatItem::Optional`] or [`OwnedFormatItem::Optional`] is passed, parsing will not + /// fail; the input will be returned as-is if the expected format is not present. + pub fn parse_item<'a>( + &mut self, + input: &'a [u8], + item: &impl sealed::AnyFormatItem, + ) -> Result<&'a [u8], error::ParseFromDescription> { + item.parse_item(self, input) + } + + /// Parse a sequence of [`FormatItem`]s or [`OwnedFormatItem`]s, mutating the struct. The + /// remaining input is returned as the `Ok` value. + /// + /// This method will fail if any of the contained [`FormatItem`]s or [`OwnedFormatItem`]s fail + /// to parse. `self` will not be mutated in this instance. + pub fn parse_items<'a>( + &mut self, + mut input: &'a [u8], + items: &[impl sealed::AnyFormatItem], + ) -> Result<&'a [u8], error::ParseFromDescription> { + // Make a copy that we can mutate. It will only be set to the user's copy if everything + // succeeds. + let mut this = *self; + for item in items { + input = this.parse_item(input, item)?; + } + *self = this; + Ok(input) + } + + /// Parse a literal byte sequence. The remaining input is returned as the `Ok` value. + pub fn parse_literal<'a>( + input: &'a [u8], + literal: &[u8], + ) -> Result<&'a [u8], error::ParseFromDescription> { + input + .strip_prefix(literal) + .ok_or(error::ParseFromDescription::InvalidLiteral) + } + + /// Parse a single component, mutating the struct. The remaining input is returned as the `Ok` + /// value. + pub fn parse_component<'a>( + &mut self, + input: &'a [u8], + component: Component, + ) -> Result<&'a [u8], error::ParseFromDescription> { + use error::ParseFromDescription::InvalidComponent; + + match component { + Component::Day(modifiers) => parse_day(input, modifiers) + .and_then(|parsed| parsed.consume_value(|value| self.set_day(value))) + .ok_or(InvalidComponent("day")), + Component::Month(modifiers) => parse_month(input, modifiers) + .and_then(|parsed| parsed.consume_value(|value| self.set_month(value))) + .ok_or(InvalidComponent("month")), + Component::Ordinal(modifiers) => parse_ordinal(input, modifiers) + .and_then(|parsed| parsed.consume_value(|value| self.set_ordinal(value))) + .ok_or(InvalidComponent("ordinal")), + Component::Weekday(modifiers) => parse_weekday(input, modifiers) + .and_then(|parsed| parsed.consume_value(|value| self.set_weekday(value))) + .ok_or(InvalidComponent("weekday")), + Component::WeekNumber(modifiers) => { + let ParsedItem(remaining, value) = + parse_week_number(input, modifiers).ok_or(InvalidComponent("week number"))?; + match modifiers.repr { + WeekNumberRepr::Iso => { + NonZeroU8::new(value).and_then(|value| self.set_iso_week_number(value)) + } + WeekNumberRepr::Sunday => self.set_sunday_week_number(value), + WeekNumberRepr::Monday => self.set_monday_week_number(value), + } + .ok_or(InvalidComponent("week number"))?; + Ok(remaining) + } + Component::Year(modifiers) => { + let ParsedItem(remaining, value) = + parse_year(input, modifiers).ok_or(InvalidComponent("year"))?; + match (modifiers.iso_week_based, modifiers.repr) { + (false, YearRepr::Full) => self.set_year(value), + (false, YearRepr::LastTwo) => self.set_year_last_two(value as _), + (true, YearRepr::Full) => self.set_iso_year(value), + (true, YearRepr::LastTwo) => self.set_iso_year_last_two(value as _), + } + .ok_or(InvalidComponent("year"))?; + Ok(remaining) + } + Component::Hour(modifiers) => { + let ParsedItem(remaining, value) = + parse_hour(input, modifiers).ok_or(InvalidComponent("hour"))?; + if modifiers.is_12_hour_clock { + NonZeroU8::new(value).and_then(|value| self.set_hour_12(value)) + } else { + self.set_hour_24(value) + } + .ok_or(InvalidComponent("hour"))?; + Ok(remaining) + } + Component::Minute(modifiers) => parse_minute(input, modifiers) + .and_then(|parsed| parsed.consume_value(|value| self.set_minute(value))) + .ok_or(InvalidComponent("minute")), + Component::Period(modifiers) => parse_period(input, modifiers) + .and_then(|parsed| { + parsed.consume_value(|value| self.set_hour_12_is_pm(value == Period::Pm)) + }) + .ok_or(InvalidComponent("period")), + Component::Second(modifiers) => parse_second(input, modifiers) + .and_then(|parsed| parsed.consume_value(|value| self.set_second(value))) + .ok_or(InvalidComponent("second")), + Component::Subsecond(modifiers) => parse_subsecond(input, modifiers) + .and_then(|parsed| parsed.consume_value(|value| self.set_subsecond(value))) + .ok_or(InvalidComponent("subsecond")), + Component::OffsetHour(modifiers) => parse_offset_hour(input, modifiers) + .and_then(|parsed| parsed.consume_value(|value| self.set_offset_hour(value))) + .ok_or(InvalidComponent("offset hour")), + Component::OffsetMinute(modifiers) => parse_offset_minute(input, modifiers) + .and_then(|parsed| { + parsed.consume_value(|value| self.set_offset_minute_signed(value)) + }) + .ok_or(InvalidComponent("offset minute")), + Component::OffsetSecond(modifiers) => parse_offset_second(input, modifiers) + .and_then(|parsed| { + parsed.consume_value(|value| self.set_offset_second_signed(value)) + }) + .ok_or(InvalidComponent("offset second")), + } + } +} + +/// Generate getters for each of the fields. +macro_rules! getters { + ($($(@$flag:ident)? $name:ident: $ty:ty),+ $(,)?) => {$( + getters!(! $(@$flag)? $name: $ty); + )*}; + (! $name:ident : $ty:ty) => { + /// Obtain the named component. + pub const fn $name(&self) -> Option<$ty> { + self.$name + } + }; + (! @$flag:ident $name:ident : $ty:ty) => { + /// Obtain the named component. + pub const fn $name(&self) -> Option<$ty> { + if self.flags & Self::$flag != Self::$flag { + None + } else { + // SAFETY: We just checked if the field is present. + Some(unsafe { self.$name.assume_init() }) + } + } + }; +} + +/// Getter methods +impl Parsed { + getters! { + @YEAR_FLAG year: i32, + @YEAR_LAST_TWO_FLAG year_last_two: u8, + @ISO_YEAR_FLAG iso_year: i32, + @ISO_YEAR_LAST_TWO_FLAG iso_year_last_two: u8, + month: Month, + @SUNDAY_WEEK_NUMBER_FLAG sunday_week_number: u8, + @MONDAY_WEEK_NUMBER_FLAG monday_week_number: u8, + iso_week_number: NonZeroU8, + weekday: Weekday, + ordinal: NonZeroU16, + day: NonZeroU8, + @HOUR_24_FLAG hour_24: u8, + hour_12: NonZeroU8, + hour_12_is_pm: bool, + @MINUTE_FLAG minute: u8, + @SECOND_FLAG second: u8, + @SUBSECOND_FLAG subsecond: u32, + @OFFSET_HOUR_FLAG offset_hour: i8, + } + + /// Obtain the absolute value of the offset minute. + #[deprecated(since = "0.3.8", note = "use `parsed.offset_minute_signed()` instead")] + pub const fn offset_minute(&self) -> Option<u8> { + Some(const_try_opt!(self.offset_minute_signed()).unsigned_abs()) + } + + /// Obtain the offset minute as an `i8`. + pub const fn offset_minute_signed(&self) -> Option<i8> { + if self.flags & Self::OFFSET_MINUTE_FLAG != Self::OFFSET_MINUTE_FLAG { + None + } else { + // SAFETY: We just checked if the field is present. + Some(unsafe { self.offset_minute.assume_init() }) + } + } + + /// Obtain the absolute value of the offset second. + #[deprecated(since = "0.3.8", note = "use `parsed.offset_second_signed()` instead")] + pub const fn offset_second(&self) -> Option<u8> { + Some(const_try_opt!(self.offset_second_signed()).unsigned_abs()) + } + + /// Obtain the offset second as an `i8`. + pub const fn offset_second_signed(&self) -> Option<i8> { + if self.flags & Self::OFFSET_SECOND_FLAG != Self::OFFSET_SECOND_FLAG { + None + } else { + // SAFETY: We just checked if the field is present. + Some(unsafe { self.offset_second.assume_init() }) + } + } + + /// Obtain whether leap seconds are permitted in the current format. + pub(crate) const fn leap_second_allowed(&self) -> bool { + self.flags & Self::LEAP_SECOND_ALLOWED_FLAG == Self::LEAP_SECOND_ALLOWED_FLAG + } +} + +/// Generate setters for each of the fields. +/// +/// This macro should only be used for fields where the value is not validated beyond its type. +macro_rules! setters { + ($($(@$flag:ident)? $setter_name:ident $name:ident: $ty:ty),+ $(,)?) => {$( + setters!(! $(@$flag)? $setter_name $name: $ty); + )*}; + (! $setter_name:ident $name:ident : $ty:ty) => { + /// Set the named component. + pub fn $setter_name(&mut self, value: $ty) -> Option<()> { + self.$name = Some(value); + Some(()) + } + }; + (! @$flag:ident $setter_name:ident $name:ident : $ty:ty) => { + /// Set the named component. + pub fn $setter_name(&mut self, value: $ty) -> Option<()> { + self.$name = MaybeUninit::new(value); + self.flags |= Self::$flag; + Some(()) + } + }; +} + +/// Setter methods +/// +/// All setters return `Option<()>`, which is `Some` if the value was set, and `None` if not. The +/// setters _may_ fail if the value is invalid, though behavior is not guaranteed. +impl Parsed { + setters! { + @YEAR_FLAG set_year year: i32, + @YEAR_LAST_TWO_FLAG set_year_last_two year_last_two: u8, + @ISO_YEAR_FLAG set_iso_year iso_year: i32, + @ISO_YEAR_LAST_TWO_FLAG set_iso_year_last_two iso_year_last_two: u8, + set_month month: Month, + @SUNDAY_WEEK_NUMBER_FLAG set_sunday_week_number sunday_week_number: u8, + @MONDAY_WEEK_NUMBER_FLAG set_monday_week_number monday_week_number: u8, + set_iso_week_number iso_week_number: NonZeroU8, + set_weekday weekday: Weekday, + set_ordinal ordinal: NonZeroU16, + set_day day: NonZeroU8, + @HOUR_24_FLAG set_hour_24 hour_24: u8, + set_hour_12 hour_12: NonZeroU8, + set_hour_12_is_pm hour_12_is_pm: bool, + @MINUTE_FLAG set_minute minute: u8, + @SECOND_FLAG set_second second: u8, + @SUBSECOND_FLAG set_subsecond subsecond: u32, + @OFFSET_HOUR_FLAG set_offset_hour offset_hour: i8, + } + + /// Set the named component. + #[deprecated( + since = "0.3.8", + note = "use `parsed.set_offset_minute_signed()` instead" + )] + pub fn set_offset_minute(&mut self, value: u8) -> Option<()> { + if value > i8::MAX as u8 { + None + } else { + self.set_offset_minute_signed(value as _) + } + } + + /// Set the `offset_minute` component. + pub fn set_offset_minute_signed(&mut self, value: i8) -> Option<()> { + self.offset_minute = MaybeUninit::new(value); + self.flags |= Self::OFFSET_MINUTE_FLAG; + Some(()) + } + + /// Set the named component. + #[deprecated( + since = "0.3.8", + note = "use `parsed.set_offset_second_signed()` instead" + )] + pub fn set_offset_second(&mut self, value: u8) -> Option<()> { + if value > i8::MAX as u8 { + None + } else { + self.set_offset_second_signed(value as _) + } + } + + /// Set the `offset_second` component. + pub fn set_offset_second_signed(&mut self, value: i8) -> Option<()> { + self.offset_second = MaybeUninit::new(value); + self.flags |= Self::OFFSET_SECOND_FLAG; + Some(()) + } + + /// Set the leap second allowed flag. + pub(crate) fn set_leap_second_allowed(&mut self, value: bool) { + if value { + self.flags |= Self::LEAP_SECOND_ALLOWED_FLAG; + } else { + self.flags &= !Self::LEAP_SECOND_ALLOWED_FLAG; + } + } +} + +/// Generate build methods for each of the fields. +/// +/// This macro should only be used for fields where the value is not validated beyond its type. +macro_rules! builders { + ($($(@$flag:ident)? $builder_name:ident $name:ident: $ty:ty),+ $(,)?) => {$( + builders!(! $(@$flag)? $builder_name $name: $ty); + )*}; + (! $builder_name:ident $name:ident : $ty:ty) => { + /// Set the named component and return `self`. + pub const fn $builder_name(mut self, value: $ty) -> Option<Self> { + self.$name = Some(value); + Some(self) + } + }; + (! @$flag:ident $builder_name:ident $name:ident : $ty:ty) => { + /// Set the named component and return `self`. + pub const fn $builder_name(mut self, value: $ty) -> Option<Self> { + self.$name = MaybeUninit::new(value); + self.flags |= Self::$flag; + Some(self) + } + }; +} + +/// Builder methods +/// +/// All builder methods return `Option<Self>`, which is `Some` if the value was set, and `None` if +/// not. The builder methods _may_ fail if the value is invalid, though behavior is not guaranteed. +impl Parsed { + builders! { + @YEAR_FLAG with_year year: i32, + @YEAR_LAST_TWO_FLAG with_year_last_two year_last_two: u8, + @ISO_YEAR_FLAG with_iso_year iso_year: i32, + @ISO_YEAR_LAST_TWO_FLAG with_iso_year_last_two iso_year_last_two: u8, + with_month month: Month, + @SUNDAY_WEEK_NUMBER_FLAG with_sunday_week_number sunday_week_number: u8, + @MONDAY_WEEK_NUMBER_FLAG with_monday_week_number monday_week_number: u8, + with_iso_week_number iso_week_number: NonZeroU8, + with_weekday weekday: Weekday, + with_ordinal ordinal: NonZeroU16, + with_day day: NonZeroU8, + @HOUR_24_FLAG with_hour_24 hour_24: u8, + with_hour_12 hour_12: NonZeroU8, + with_hour_12_is_pm hour_12_is_pm: bool, + @MINUTE_FLAG with_minute minute: u8, + @SECOND_FLAG with_second second: u8, + @SUBSECOND_FLAG with_subsecond subsecond: u32, + @OFFSET_HOUR_FLAG with_offset_hour offset_hour: i8, + } + + /// Set the named component and return `self`. + #[deprecated( + since = "0.3.8", + note = "use `parsed.with_offset_minute_signed()` instead" + )] + pub const fn with_offset_minute(self, value: u8) -> Option<Self> { + if value > i8::MAX as u8 { + None + } else { + self.with_offset_minute_signed(value as _) + } + } + + /// Set the `offset_minute` component and return `self`. + pub const fn with_offset_minute_signed(mut self, value: i8) -> Option<Self> { + self.offset_minute = MaybeUninit::new(value); + self.flags |= Self::OFFSET_MINUTE_FLAG; + Some(self) + } + + /// Set the named component and return `self`. + #[deprecated( + since = "0.3.8", + note = "use `parsed.with_offset_second_signed()` instead" + )] + pub const fn with_offset_second(self, value: u8) -> Option<Self> { + if value > i8::MAX as u8 { + None + } else { + self.with_offset_second_signed(value as _) + } + } + + /// Set the `offset_second` component and return `self`. + pub const fn with_offset_second_signed(mut self, value: i8) -> Option<Self> { + self.offset_second = MaybeUninit::new(value); + self.flags |= Self::OFFSET_SECOND_FLAG; + Some(self) + } +} + +impl TryFrom<Parsed> for Date { + type Error = error::TryFromParsed; + + fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { + /// Match on the components that need to be present. + macro_rules! match_ { + (_ => $catch_all:expr $(,)?) => { + $catch_all + }; + (($($name:ident),* $(,)?) => $arm:expr, $($rest:tt)*) => { + if let ($(Some($name)),*) = ($(parsed.$name()),*) { + $arm + } else { + match_!($($rest)*) + } + }; + } + + /// Get the value needed to adjust the ordinal day for Sunday and Monday-based week + /// numbering. + const fn adjustment(year: i32) -> i16 { + match Date::__from_ordinal_date_unchecked(year, 1).weekday() { + Weekday::Monday => 7, + Weekday::Tuesday => 1, + Weekday::Wednesday => 2, + Weekday::Thursday => 3, + Weekday::Friday => 4, + Weekday::Saturday => 5, + Weekday::Sunday => 6, + } + } + + // TODO Only the basics have been covered. There are many other valid values that are not + // currently constructed from the information known. + + match_! { + (year, ordinal) => Ok(Self::from_ordinal_date(year, ordinal.get())?), + (year, month, day) => Ok(Self::from_calendar_date(year, month, day.get())?), + (iso_year, iso_week_number, weekday) => Ok(Self::from_iso_week_date( + iso_year, + iso_week_number.get(), + weekday, + )?), + (year, sunday_week_number, weekday) => Ok(Self::from_ordinal_date( + year, + (sunday_week_number as i16 * 7 + weekday.number_days_from_sunday() as i16 + - adjustment(year) + + 1) as u16, + )?), + (year, monday_week_number, weekday) => Ok(Self::from_ordinal_date( + year, + (monday_week_number as i16 * 7 + weekday.number_days_from_monday() as i16 + - adjustment(year) + + 1) as u16, + )?), + _ => Err(InsufficientInformation), + } + } +} + +impl TryFrom<Parsed> for Time { + type Error = error::TryFromParsed; + + fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { + let hour = match (parsed.hour_24(), parsed.hour_12(), parsed.hour_12_is_pm()) { + (Some(hour), _, _) => hour, + (_, Some(hour), Some(false)) if hour.get() == 12 => 0, + (_, Some(hour), Some(true)) if hour.get() == 12 => 12, + (_, Some(hour), Some(false)) => hour.get(), + (_, Some(hour), Some(true)) => hour.get() + 12, + _ => return Err(InsufficientInformation), + }; + if parsed.hour_24().is_none() + && parsed.hour_12().is_some() + && parsed.hour_12_is_pm().is_some() + && parsed.minute().is_none() + && parsed.second().is_none() + && parsed.subsecond().is_none() + { + return Ok(Self::from_hms_nano(hour, 0, 0, 0)?); + } + let minute = parsed.minute().ok_or(InsufficientInformation)?; + let second = parsed.second().unwrap_or(0); + let subsecond = parsed.subsecond().unwrap_or(0); + Ok(Self::from_hms_nano(hour, minute, second, subsecond)?) + } +} + +impl TryFrom<Parsed> for UtcOffset { + type Error = error::TryFromParsed; + + fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { + let hour = parsed.offset_hour().ok_or(InsufficientInformation)?; + let minute = parsed.offset_minute_signed().unwrap_or(0); + let second = parsed.offset_second_signed().unwrap_or(0); + + Self::from_hms(hour, minute, second).map_err(|mut err| { + // Provide the user a more accurate error. + if err.name == "hours" { + err.name = "offset hour"; + } else if err.name == "minutes" { + err.name = "offset minute"; + } else if err.name == "seconds" { + err.name = "offset second"; + } + err.into() + }) + } +} + +impl TryFrom<Parsed> for PrimitiveDateTime { + type Error = error::TryFromParsed; + + fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { + Ok(Self::new(parsed.try_into()?, parsed.try_into()?)) + } +} + +impl TryFrom<Parsed> for OffsetDateTime { + type Error = error::TryFromParsed; + + #[allow(clippy::unwrap_in_result)] // We know the values are valid. + fn try_from(mut parsed: Parsed) -> Result<Self, Self::Error> { + // Some well-known formats explicitly allow leap seconds. We don't currently support them, + // so treat it as the nearest preceding moment that can be represented. Because leap seconds + // always fall at the end of a month UTC, reject any that are at other times. + let leap_second_input = if parsed.leap_second_allowed() && parsed.second() == Some(60) { + parsed.set_second(59).expect("59 is a valid second"); + parsed + .set_subsecond(999_999_999) + .expect("999_999_999 is a valid subsecond"); + true + } else { + false + }; + let dt = PrimitiveDateTime::try_from(parsed)?.assume_offset(parsed.try_into()?); + if leap_second_input && !dt.is_valid_leap_second_stand_in() { + return Err(error::TryFromParsed::ComponentRange( + error::ComponentRange { + name: "second", + minimum: 0, + maximum: 59, + value: 60, + conditional_range: true, + }, + )); + } + Ok(dt) + } +} diff --git a/third_party/rust/time/src/parsing/shim.rs b/third_party/rust/time/src/parsing/shim.rs new file mode 100644 index 0000000000..00aaf4852b --- /dev/null +++ b/third_party/rust/time/src/parsing/shim.rs @@ -0,0 +1,50 @@ +//! Extension traits for things either not implemented or not yet stable in the MSRV. + +/// Equivalent of `foo.parse()` for slices. +pub(crate) trait IntegerParseBytes<T> { + #[allow(clippy::missing_docs_in_private_items)] + fn parse_bytes(&self) -> Option<T>; +} + +impl<T: Integer> IntegerParseBytes<T> for [u8] { + fn parse_bytes(&self) -> Option<T> { + T::parse_bytes(self) + } +} + +/// Marker trait for all integer types, including `NonZero*` +pub(crate) trait Integer: Sized { + #[allow(clippy::missing_docs_in_private_items)] + fn parse_bytes(src: &[u8]) -> Option<Self>; +} + +/// Parse the given types from bytes. +macro_rules! impl_parse_bytes { + ($($t:ty)*) => ($( + impl Integer for $t { + #[allow(trivial_numeric_casts)] + fn parse_bytes(src: &[u8]) -> Option<Self> { + src.iter().try_fold::<Self, _, _>(0, |result, c| { + result.checked_mul(10)?.checked_add((c - b'0') as Self) + }) + } + } + )*) +} +impl_parse_bytes! { u8 u16 u32 } + +/// Parse the given types from bytes. +macro_rules! impl_parse_bytes_nonzero { + ($($t:ty)*) => {$( + impl Integer for $t { + fn parse_bytes(src: &[u8]) -> Option<Self> { + Self::new(src.parse_bytes()?) + } + } + )*} +} + +impl_parse_bytes_nonzero! { + core::num::NonZeroU8 + core::num::NonZeroU16 +} diff --git a/third_party/rust/time/src/primitive_date_time.rs b/third_party/rust/time/src/primitive_date_time.rs new file mode 100644 index 0000000000..6e842092ab --- /dev/null +++ b/third_party/rust/time/src/primitive_date_time.rs @@ -0,0 +1,937 @@ +//! The [`PrimitiveDateTime`] struct and its associated `impl`s. + +use core::fmt; +use core::ops::{Add, Sub}; +use core::time::Duration as StdDuration; +#[cfg(feature = "formatting")] +use std::io; + +#[cfg(feature = "formatting")] +use crate::formatting::Formattable; +#[cfg(feature = "parsing")] +use crate::parsing::Parsable; +use crate::{error, util, Date, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday}; + +/// Combined date and time. +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct PrimitiveDateTime { + #[allow(clippy::missing_docs_in_private_items)] + pub(crate) date: Date, + #[allow(clippy::missing_docs_in_private_items)] + pub(crate) time: Time, +} + +impl PrimitiveDateTime { + /// The smallest value that can be represented by `PrimitiveDateTime`. + /// + /// Depending on `large-dates` feature flag, value of this constant may vary. + /// + /// 1. With `large-dates` disabled it is equal to `-9999-01-01 00:00:00.0` + /// 2. With `large-dates` enabled it is equal to `-999999-01-01 00:00:00.0` + /// + /// ```rust + /// # use time::PrimitiveDateTime; + /// # use time_macros::datetime; + #[cfg_attr( + feature = "large-dates", + doc = "// Assuming `large-dates` feature is enabled." + )] + #[cfg_attr( + feature = "large-dates", + doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-999999-01-01 0:00));" + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = "// Assuming `large-dates` feature is disabled." + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-9999-01-01 0:00));" + )] + /// ``` + pub const MIN: Self = Self::new(Date::MIN, Time::MIN); + + /// The largest value that can be represented by `PrimitiveDateTime`. + /// + /// Depending on `large-dates` feature flag, value of this constant may vary. + /// + /// 1. With `large-dates` disabled it is equal to `9999-12-31 23:59:59.999_999_999` + /// 2. With `large-dates` enabled it is equal to `999999-12-31 23:59:59.999_999_999` + /// + /// ```rust + /// # use time::PrimitiveDateTime; + /// # use time_macros::datetime; + #[cfg_attr( + feature = "large-dates", + doc = "// Assuming `large-dates` feature is enabled." + )] + #[cfg_attr( + feature = "large-dates", + doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+999999-12-31 23:59:59.999_999_999));" + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = "// Assuming `large-dates` feature is disabled." + )] + #[cfg_attr( + not(feature = "large-dates"), + doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+9999-12-31 23:59:59.999_999_999));" + )] + /// ``` + pub const MAX: Self = Self::new(Date::MAX, Time::MAX); + + /// Create a new `PrimitiveDateTime` from the provided [`Date`] and [`Time`]. + /// + /// ```rust + /// # use time::PrimitiveDateTime; + /// # use time_macros::{date, datetime, time}; + /// assert_eq!( + /// PrimitiveDateTime::new(date!(2019-01-01), time!(0:00)), + /// datetime!(2019-01-01 0:00), + /// ); + /// ``` + pub const fn new(date: Date, time: Time) -> Self { + Self { date, time } + } + + // region: component getters + /// Get the [`Date`] component of the `PrimitiveDateTime`. + /// + /// ```rust + /// # use time_macros::{date, datetime}; + /// assert_eq!(datetime!(2019-01-01 0:00).date(), date!(2019-01-01)); + /// ``` + pub const fn date(self) -> Date { + self.date + } + + /// Get the [`Time`] component of the `PrimitiveDateTime`. + /// + /// ```rust + /// # use time_macros::{datetime, time}; + /// assert_eq!(datetime!(2019-01-01 0:00).time(), time!(0:00)); + pub const fn time(self) -> Time { + self.time + } + // endregion component getters + + // region: date getters + /// Get the year of the date. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).year(), 2019); + /// assert_eq!(datetime!(2019-12-31 0:00).year(), 2019); + /// assert_eq!(datetime!(2020-01-01 0:00).year(), 2020); + /// ``` + pub const fn year(self) -> i32 { + self.date.year() + } + + /// Get the month of the date. + /// + /// ```rust + /// # use time::Month; + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).month(), Month::January); + /// assert_eq!(datetime!(2019-12-31 0:00).month(), Month::December); + /// ``` + pub const fn month(self) -> Month { + self.date.month() + } + + /// Get the day of the date. + /// + /// The returned value will always be in the range `1..=31`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).day(), 1); + /// assert_eq!(datetime!(2019-12-31 0:00).day(), 31); + /// ``` + pub const fn day(self) -> u8 { + self.date.day() + } + + /// Get the day of the year. + /// + /// The returned value will always be in the range `1..=366` (`1..=365` for common years). + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).ordinal(), 1); + /// assert_eq!(datetime!(2019-12-31 0:00).ordinal(), 365); + /// ``` + pub const fn ordinal(self) -> u16 { + self.date.ordinal() + } + + /// Get the ISO week number. + /// + /// The returned value will always be in the range `1..=53`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).iso_week(), 1); + /// assert_eq!(datetime!(2019-10-04 0:00).iso_week(), 40); + /// assert_eq!(datetime!(2020-01-01 0:00).iso_week(), 1); + /// assert_eq!(datetime!(2020-12-31 0:00).iso_week(), 53); + /// assert_eq!(datetime!(2021-01-01 0:00).iso_week(), 53); + /// ``` + pub const fn iso_week(self) -> u8 { + self.date.iso_week() + } + + /// Get the week number where week 1 begins on the first Sunday. + /// + /// The returned value will always be in the range `0..=53`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).sunday_based_week(), 0); + /// assert_eq!(datetime!(2020-01-01 0:00).sunday_based_week(), 0); + /// assert_eq!(datetime!(2020-12-31 0:00).sunday_based_week(), 52); + /// assert_eq!(datetime!(2021-01-01 0:00).sunday_based_week(), 0); + /// ``` + pub const fn sunday_based_week(self) -> u8 { + self.date.sunday_based_week() + } + + /// Get the week number where week 1 begins on the first Monday. + /// + /// The returned value will always be in the range `0..=53`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).monday_based_week(), 0); + /// assert_eq!(datetime!(2020-01-01 0:00).monday_based_week(), 0); + /// assert_eq!(datetime!(2020-12-31 0:00).monday_based_week(), 52); + /// assert_eq!(datetime!(2021-01-01 0:00).monday_based_week(), 0); + /// ``` + pub const fn monday_based_week(self) -> u8 { + self.date.monday_based_week() + } + + /// Get the year, month, and day. + /// + /// ```rust + /// # use time::Month; + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2019-01-01 0:00).to_calendar_date(), + /// (2019, Month::January, 1) + /// ); + /// ``` + pub const fn to_calendar_date(self) -> (i32, Month, u8) { + self.date.to_calendar_date() + } + + /// Get the year and ordinal day number. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).to_ordinal_date(), (2019, 1)); + /// ``` + pub const fn to_ordinal_date(self) -> (i32, u16) { + self.date.to_ordinal_date() + } + + /// Get the ISO 8601 year, week number, and weekday. + /// + /// ```rust + /// # use time::Weekday::*; + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2019-01-01 0:00).to_iso_week_date(), + /// (2019, 1, Tuesday) + /// ); + /// assert_eq!( + /// datetime!(2019-10-04 0:00).to_iso_week_date(), + /// (2019, 40, Friday) + /// ); + /// assert_eq!( + /// datetime!(2020-01-01 0:00).to_iso_week_date(), + /// (2020, 1, Wednesday) + /// ); + /// assert_eq!( + /// datetime!(2020-12-31 0:00).to_iso_week_date(), + /// (2020, 53, Thursday) + /// ); + /// assert_eq!( + /// datetime!(2021-01-01 0:00).to_iso_week_date(), + /// (2020, 53, Friday) + /// ); + /// ``` + pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { + self.date.to_iso_week_date() + } + + /// Get the weekday. + /// + /// ```rust + /// # use time::Weekday::*; + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).weekday(), Tuesday); + /// assert_eq!(datetime!(2019-02-01 0:00).weekday(), Friday); + /// assert_eq!(datetime!(2019-03-01 0:00).weekday(), Friday); + /// assert_eq!(datetime!(2019-04-01 0:00).weekday(), Monday); + /// assert_eq!(datetime!(2019-05-01 0:00).weekday(), Wednesday); + /// assert_eq!(datetime!(2019-06-01 0:00).weekday(), Saturday); + /// assert_eq!(datetime!(2019-07-01 0:00).weekday(), Monday); + /// assert_eq!(datetime!(2019-08-01 0:00).weekday(), Thursday); + /// assert_eq!(datetime!(2019-09-01 0:00).weekday(), Sunday); + /// assert_eq!(datetime!(2019-10-01 0:00).weekday(), Tuesday); + /// assert_eq!(datetime!(2019-11-01 0:00).weekday(), Friday); + /// assert_eq!(datetime!(2019-12-01 0:00).weekday(), Sunday); + /// ``` + pub const fn weekday(self) -> Weekday { + self.date.weekday() + } + + /// Get the Julian day for the date. The time is not taken into account for this calculation. + /// + /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is + /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(-4713-11-24 0:00).to_julian_day(), 0); + /// assert_eq!(datetime!(2000-01-01 0:00).to_julian_day(), 2_451_545); + /// assert_eq!(datetime!(2019-01-01 0:00).to_julian_day(), 2_458_485); + /// assert_eq!(datetime!(2019-12-31 0:00).to_julian_day(), 2_458_849); + /// ``` + pub const fn to_julian_day(self) -> i32 { + self.date.to_julian_day() + } + // endregion date getters + + // region: time getters + /// Get the clock hour, minute, and second. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms(), (0, 0, 0)); + /// assert_eq!(datetime!(2020-01-01 23:59:59).as_hms(), (23, 59, 59)); + /// ``` + pub const fn as_hms(self) -> (u8, u8, u8) { + self.time.as_hms() + } + + /// Get the clock hour, minute, second, and millisecond. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_milli(), (0, 0, 0, 0)); + /// assert_eq!( + /// datetime!(2020-01-01 23:59:59.999).as_hms_milli(), + /// (23, 59, 59, 999) + /// ); + /// ``` + pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) { + self.time.as_hms_milli() + } + + /// Get the clock hour, minute, second, and microsecond. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_micro(), (0, 0, 0, 0)); + /// assert_eq!( + /// datetime!(2020-01-01 23:59:59.999_999).as_hms_micro(), + /// (23, 59, 59, 999_999) + /// ); + /// ``` + pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) { + self.time.as_hms_micro() + } + + /// Get the clock hour, minute, second, and nanosecond. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_nano(), (0, 0, 0, 0)); + /// assert_eq!( + /// datetime!(2020-01-01 23:59:59.999_999_999).as_hms_nano(), + /// (23, 59, 59, 999_999_999) + /// ); + /// ``` + pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) { + self.time.as_hms_nano() + } + + /// Get the clock hour. + /// + /// The returned value will always be in the range `0..24`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).hour(), 0); + /// assert_eq!(datetime!(2019-01-01 23:59:59).hour(), 23); + /// ``` + pub const fn hour(self) -> u8 { + self.time.hour() + } + + /// Get the minute within the hour. + /// + /// The returned value will always be in the range `0..60`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).minute(), 0); + /// assert_eq!(datetime!(2019-01-01 23:59:59).minute(), 59); + /// ``` + pub const fn minute(self) -> u8 { + self.time.minute() + } + + /// Get the second within the minute. + /// + /// The returned value will always be in the range `0..60`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).second(), 0); + /// assert_eq!(datetime!(2019-01-01 23:59:59).second(), 59); + /// ``` + pub const fn second(self) -> u8 { + self.time.second() + } + + /// Get the milliseconds within the second. + /// + /// The returned value will always be in the range `0..1_000`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).millisecond(), 0); + /// assert_eq!(datetime!(2019-01-01 23:59:59.999).millisecond(), 999); + /// ``` + pub const fn millisecond(self) -> u16 { + self.time.millisecond() + } + + /// Get the microseconds within the second. + /// + /// The returned value will always be in the range `0..1_000_000`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).microsecond(), 0); + /// assert_eq!( + /// datetime!(2019-01-01 23:59:59.999_999).microsecond(), + /// 999_999 + /// ); + /// ``` + pub const fn microsecond(self) -> u32 { + self.time.microsecond() + } + + /// Get the nanoseconds within the second. + /// + /// The returned value will always be in the range `0..1_000_000_000`. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!(datetime!(2019-01-01 0:00).nanosecond(), 0); + /// assert_eq!( + /// datetime!(2019-01-01 23:59:59.999_999_999).nanosecond(), + /// 999_999_999, + /// ); + /// ``` + pub const fn nanosecond(self) -> u32 { + self.time.nanosecond() + } + // endregion time getters + + // region: attach offset + /// Assuming that the existing `PrimitiveDateTime` represents a moment in the provided + /// [`UtcOffset`], return an [`OffsetDateTime`]. + /// + /// ```rust + /// # use time_macros::{datetime, offset}; + /// assert_eq!( + /// datetime!(2019-01-01 0:00) + /// .assume_offset(offset!(UTC)) + /// .unix_timestamp(), + /// 1_546_300_800, + /// ); + /// assert_eq!( + /// datetime!(2019-01-01 0:00) + /// .assume_offset(offset!(-1)) + /// .unix_timestamp(), + /// 1_546_304_400, + /// ); + /// ``` + pub const fn assume_offset(self, offset: UtcOffset) -> OffsetDateTime { + OffsetDateTime { + local_datetime: self, + offset, + } + } + + /// Assuming that the existing `PrimitiveDateTime` represents a moment in UTC, return an + /// [`OffsetDateTime`]. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2019-01-01 0:00).assume_utc().unix_timestamp(), + /// 1_546_300_800, + /// ); + /// ``` + pub const fn assume_utc(self) -> OffsetDateTime { + self.assume_offset(UtcOffset::UTC) + } + // endregion attach offset + + // region: checked arithmetic + /// Computes `self + duration`, returning `None` if an overflow occurred. + /// + /// ``` + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::datetime; + /// let datetime = Date::MIN.midnight(); + /// assert_eq!(datetime.checked_add((-2).days()), None); + /// + /// let datetime = Date::MAX.midnight(); + /// assert_eq!(datetime.checked_add(1.days()), None); + /// + /// assert_eq!( + /// datetime!(2019 - 11 - 25 15:30).checked_add(27.hours()), + /// Some(datetime!(2019 - 11 - 26 18:30)) + /// ); + /// ``` + pub const fn checked_add(self, duration: Duration) -> Option<Self> { + let (date_adjustment, time) = self.time.adjusting_add(duration); + let date = const_try_opt!(self.date.checked_add(duration)); + + Some(Self { + date: match date_adjustment { + util::DateAdjustment::Previous => const_try_opt!(date.previous_day()), + util::DateAdjustment::Next => const_try_opt!(date.next_day()), + util::DateAdjustment::None => date, + }, + time, + }) + } + + /// Computes `self - duration`, returning `None` if an overflow occurred. + /// + /// ``` + /// # use time::{Date, ext::NumericalDuration}; + /// # use time_macros::datetime; + /// let datetime = Date::MIN.midnight(); + /// assert_eq!(datetime.checked_sub(2.days()), None); + /// + /// let datetime = Date::MAX.midnight(); + /// assert_eq!(datetime.checked_sub((-1).days()), None); + /// + /// assert_eq!( + /// datetime!(2019 - 11 - 25 15:30).checked_sub(27.hours()), + /// Some(datetime!(2019 - 11 - 24 12:30)) + /// ); + /// ``` + pub const fn checked_sub(self, duration: Duration) -> Option<Self> { + let (date_adjustment, time) = self.time.adjusting_sub(duration); + let date = const_try_opt!(self.date.checked_sub(duration)); + + Some(Self { + date: match date_adjustment { + util::DateAdjustment::Previous => const_try_opt!(date.previous_day()), + util::DateAdjustment::Next => const_try_opt!(date.next_day()), + util::DateAdjustment::None => date, + }, + time, + }) + } + // endregion: checked arithmetic + + // region: saturating arithmetic + /// Computes `self + duration`, saturating value on overflow. + /// + /// ``` + /// # use time::{PrimitiveDateTime, ext::NumericalDuration}; + /// # use time_macros::datetime; + /// assert_eq!( + /// PrimitiveDateTime::MIN.saturating_add((-2).days()), + /// PrimitiveDateTime::MIN + /// ); + /// + /// assert_eq!( + /// PrimitiveDateTime::MAX.saturating_add(2.days()), + /// PrimitiveDateTime::MAX + /// ); + /// + /// assert_eq!( + /// datetime!(2019 - 11 - 25 15:30).saturating_add(27.hours()), + /// datetime!(2019 - 11 - 26 18:30) + /// ); + /// ``` + pub const fn saturating_add(self, duration: Duration) -> Self { + if let Some(datetime) = self.checked_add(duration) { + datetime + } else if duration.is_negative() { + Self::MIN + } else { + Self::MAX + } + } + + /// Computes `self - duration`, saturating value on overflow. + /// + /// ``` + /// # use time::{PrimitiveDateTime, ext::NumericalDuration}; + /// # use time_macros::datetime; + /// assert_eq!( + /// PrimitiveDateTime::MIN.saturating_sub(2.days()), + /// PrimitiveDateTime::MIN + /// ); + /// + /// assert_eq!( + /// PrimitiveDateTime::MAX.saturating_sub((-2).days()), + /// PrimitiveDateTime::MAX + /// ); + /// + /// assert_eq!( + /// datetime!(2019 - 11 - 25 15:30).saturating_sub(27.hours()), + /// datetime!(2019 - 11 - 24 12:30) + /// ); + /// ``` + pub const fn saturating_sub(self, duration: Duration) -> Self { + if let Some(datetime) = self.checked_sub(duration) { + datetime + } else if duration.is_negative() { + Self::MAX + } else { + Self::MIN + } + } + // endregion: saturating arithmetic +} + +// region: replacement +/// Methods that replace part of the `PrimitiveDateTime`. +impl PrimitiveDateTime { + /// Replace the time, preserving the date. + /// + /// ```rust + /// # use time_macros::{datetime, time}; + /// assert_eq!( + /// datetime!(2020-01-01 17:00).replace_time(time!(5:00)), + /// datetime!(2020-01-01 5:00) + /// ); + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_time(self, time: Time) -> Self { + self.date.with_time(time) + } + + /// Replace the date, preserving the time. + /// + /// ```rust + /// # use time_macros::{datetime, date}; + /// assert_eq!( + /// datetime!(2020-01-01 12:00).replace_date(date!(2020-01-30)), + /// datetime!(2020-01-30 12:00) + /// ); + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_date(self, date: Date) -> Self { + date.with_time(self.time) + } + + /// Replace the year. The month and day will be unchanged. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 12:00).replace_year(2019), + /// Ok(datetime!(2019 - 02 - 18 12:00)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 12:00).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year + /// assert!(datetime!(2022 - 02 - 18 12:00).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> { + Ok(const_try!(self.date.replace_year(year)).with_time(self.time)) + } + + /// Replace the month of the year. + /// + /// ```rust + /// # use time_macros::datetime; + /// # use time::Month; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 12:00).replace_month(Month::January), + /// Ok(datetime!(2022 - 01 - 18 12:00)) + /// ); + /// assert!(datetime!(2022 - 01 - 30 12:00).replace_month(Month::February).is_err()); // 30 isn't a valid day in February + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> { + Ok(const_try!(self.date.replace_month(month)).with_time(self.time)) + } + + /// Replace the day of the month. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 12:00).replace_day(1), + /// Ok(datetime!(2022 - 02 - 01 12:00)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 12:00).replace_day(0).is_err()); // 00 isn't a valid day + /// assert!(datetime!(2022 - 02 - 18 12:00).replace_day(30).is_err()); // 30 isn't a valid day in February + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> { + Ok(const_try!(self.date.replace_day(day)).with_time(self.time)) + } + + /// Replace the clock hour. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_hour(7), + /// Ok(datetime!(2022 - 02 - 18 07:02:03.004_005_006)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_hour(24).is_err()); // 24 isn't a valid hour + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> { + Ok(self + .date() + .with_time(const_try!(self.time.replace_hour(hour)))) + } + + /// Replace the minutes within the hour. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_minute(7), + /// Ok(datetime!(2022 - 02 - 18 01:07:03.004_005_006)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_minute(60).is_err()); // 60 isn't a valid minute + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> { + Ok(self + .date() + .with_time(const_try!(self.time.replace_minute(minute)))) + } + + /// Replace the seconds within the minute. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_second(7), + /// Ok(datetime!(2022 - 02 - 18 01:02:07.004_005_006)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_second(60).is_err()); // 60 isn't a valid second + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> { + Ok(self + .date() + .with_time(const_try!(self.time.replace_second(second)))) + } + + /// Replace the milliseconds within the second. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_millisecond(7), + /// Ok(datetime!(2022 - 02 - 18 01:02:03.007)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_millisecond( + self, + millisecond: u16, + ) -> Result<Self, error::ComponentRange> { + Ok(self + .date() + .with_time(const_try!(self.time.replace_millisecond(millisecond)))) + } + + /// Replace the microseconds within the second. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_microsecond(7_008), + /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_microsecond( + self, + microsecond: u32, + ) -> Result<Self, error::ComponentRange> { + Ok(self + .date() + .with_time(const_try!(self.time.replace_microsecond(microsecond)))) + } + + /// Replace the nanoseconds within the second. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_nanosecond(7_008_009), + /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008_009)) + /// ); + /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond + /// ``` + #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] + pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> { + Ok(self + .date() + .with_time(const_try!(self.time.replace_nanosecond(nanosecond)))) + } +} +// endregion replacement + +// region: formatting & parsing +#[cfg(feature = "formatting")] +impl PrimitiveDateTime { + /// Format the `PrimitiveDateTime` using the provided [format + /// description](crate::format_description). + pub fn format_into( + self, + output: &mut impl io::Write, + format: &(impl Formattable + ?Sized), + ) -> Result<usize, error::Format> { + format.format_into(output, Some(self.date), Some(self.time), None) + } + + /// Format the `PrimitiveDateTime` using the provided [format + /// description](crate::format_description). + /// + /// ```rust + /// # use time::format_description; + /// # use time_macros::datetime; + /// let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]")?; + /// assert_eq!( + /// datetime!(2020-01-02 03:04:05).format(&format)?, + /// "2020-01-02 03:04:05" + /// ); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { + format.format(Some(self.date), Some(self.time), None) + } +} + +#[cfg(feature = "parsing")] +impl PrimitiveDateTime { + /// Parse a `PrimitiveDateTime` from the input using the provided [format + /// description](crate::format_description). + /// + /// ```rust + /// # use time::PrimitiveDateTime; + /// # use time_macros::{datetime, format_description}; + /// let format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); + /// assert_eq!( + /// PrimitiveDateTime::parse("2020-01-02 03:04:05", &format)?, + /// datetime!(2020-01-02 03:04:05) + /// ); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn parse( + input: &str, + description: &(impl Parsable + ?Sized), + ) -> Result<Self, error::Parse> { + description.parse_date_time(input.as_bytes()) + } +} + +impl fmt::Display for PrimitiveDateTime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.date, self.time) + } +} + +impl fmt::Debug for PrimitiveDateTime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} +// endregion formatting & parsing + +// region: trait impls +impl Add<Duration> for PrimitiveDateTime { + type Output = Self; + + fn add(self, duration: Duration) -> Self::Output { + self.checked_add(duration) + .expect("resulting value is out of range") + } +} + +impl Add<StdDuration> for PrimitiveDateTime { + type Output = Self; + + fn add(self, duration: StdDuration) -> Self::Output { + let (is_next_day, time) = self.time.adjusting_add_std(duration); + + Self { + date: if is_next_day { + (self.date + duration) + .next_day() + .expect("resulting value is out of range") + } else { + self.date + duration + }, + time, + } + } +} + +impl_add_assign!(PrimitiveDateTime: Duration, StdDuration); + +impl Sub<Duration> for PrimitiveDateTime { + type Output = Self; + + fn sub(self, duration: Duration) -> Self::Output { + self.checked_sub(duration) + .expect("resulting value is out of range") + } +} + +impl Sub<StdDuration> for PrimitiveDateTime { + type Output = Self; + + fn sub(self, duration: StdDuration) -> Self::Output { + let (is_previous_day, time) = self.time.adjusting_sub_std(duration); + + Self { + date: if is_previous_day { + (self.date - duration) + .previous_day() + .expect("resulting value is out of range") + } else { + self.date - duration + }, + time, + } + } +} + +impl_sub_assign!(PrimitiveDateTime: Duration, StdDuration); + +impl Sub for PrimitiveDateTime { + type Output = Duration; + + fn sub(self, rhs: Self) -> Self::Output { + (self.date - rhs.date) + (self.time - rhs.time) + } +} +// endregion trait impls diff --git a/third_party/rust/time/src/quickcheck.rs b/third_party/rust/time/src/quickcheck.rs new file mode 100644 index 0000000000..707f3e0a97 --- /dev/null +++ b/third_party/rust/time/src/quickcheck.rs @@ -0,0 +1,220 @@ +//! Implementations of the [`quickcheck::Arbitrary`](quickcheck::Arbitrary) trait. +//! +//! This enables users to write tests such as this, and have test values provided automatically: +//! +//! ``` +//! # #![allow(dead_code)] +//! use quickcheck::quickcheck; +//! use time::Date; +//! +//! struct DateRange { +//! from: Date, +//! to: Date, +//! } +//! +//! impl DateRange { +//! fn new(from: Date, to: Date) -> Result<Self, ()> { +//! Ok(DateRange { from, to }) +//! } +//! } +//! +//! quickcheck! { +//! fn date_range_is_well_defined(from: Date, to: Date) -> bool { +//! let r = DateRange::new(from, to); +//! if from <= to { +//! r.is_ok() +//! } else { +//! r.is_err() +//! } +//! } +//! } +//! ``` +//! +//! An implementation for `Instant` is intentionally omitted since its values are only meaningful in +//! relation to a [`Duration`], and obtaining an `Instant` from a [`Duration`] is very simple +//! anyway. + +use alloc::boxed::Box; + +use quickcheck::{empty_shrinker, single_shrinker, Arbitrary, Gen}; + +use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; + +/// Obtain an arbitrary value between the minimum and maximum inclusive. +macro_rules! arbitrary_between { + ($type:ty; $gen:expr, $min:expr, $max:expr) => {{ + let min = $min; + let max = $max; + let range = max - min; + <$type>::arbitrary($gen).rem_euclid(range + 1) + min + }}; +} + +impl Arbitrary for Date { + fn arbitrary(g: &mut Gen) -> Self { + Self::from_julian_day_unchecked(arbitrary_between!( + i32; + g, + Self::MIN.to_julian_day(), + Self::MAX.to_julian_day() + )) + } + + fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { + Box::new( + self.to_ordinal_date() + .shrink() + .flat_map(|(year, ordinal)| Self::from_ordinal_date(year, ordinal)), + ) + } +} + +impl Arbitrary for Duration { + fn arbitrary(g: &mut Gen) -> Self { + Self::nanoseconds_i128(arbitrary_between!( + i128; + g, + Self::MIN.whole_nanoseconds(), + Self::MAX.whole_nanoseconds() + )) + } + + fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { + Box::new( + (self.subsec_nanoseconds(), self.whole_seconds()) + .shrink() + .map(|(mut nanoseconds, seconds)| { + // Coerce the sign if necessary. + if (seconds > 0 && nanoseconds < 0) || (seconds < 0 && nanoseconds > 0) { + nanoseconds *= -1; + } + + Self::new_unchecked(seconds, nanoseconds) + }), + ) + } +} + +impl Arbitrary for Time { + fn arbitrary(g: &mut Gen) -> Self { + Self::__from_hms_nanos_unchecked( + arbitrary_between!(u8; g, 0, 23), + arbitrary_between!(u8; g, 0, 59), + arbitrary_between!(u8; g, 0, 59), + arbitrary_between!(u32; g, 0, 999_999_999), + ) + } + + fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { + Box::new( + self.as_hms_nano() + .shrink() + .map(|(hour, minute, second, nanosecond)| { + Self::__from_hms_nanos_unchecked(hour, minute, second, nanosecond) + }), + ) + } +} + +impl Arbitrary for PrimitiveDateTime { + fn arbitrary(g: &mut Gen) -> Self { + Self::new(<_>::arbitrary(g), <_>::arbitrary(g)) + } + + fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { + Box::new( + (self.date, self.time) + .shrink() + .map(|(date, time)| Self { date, time }), + ) + } +} + +impl Arbitrary for UtcOffset { + fn arbitrary(g: &mut Gen) -> Self { + let seconds = arbitrary_between!(i32; g, -86_399, 86_399); + Self::__from_hms_unchecked( + (seconds / 3600) as _, + ((seconds % 3600) / 60) as _, + (seconds % 60) as _, + ) + } + + fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { + Box::new( + self.as_hms().shrink().map(|(hours, minutes, seconds)| { + Self::__from_hms_unchecked(hours, minutes, seconds) + }), + ) + } +} + +impl Arbitrary for OffsetDateTime { + fn arbitrary(g: &mut Gen) -> Self { + let datetime = PrimitiveDateTime::arbitrary(g); + datetime.assume_offset(<_>::arbitrary(g)) + } + + fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { + Box::new( + (self.local_datetime, self.offset) + .shrink() + .map(|(local_datetime, offset)| local_datetime.assume_offset(offset)), + ) + } +} + +impl Arbitrary for Weekday { + fn arbitrary(g: &mut Gen) -> Self { + use Weekday::*; + match arbitrary_between!(u8; g, 0, 6) { + 0 => Monday, + 1 => Tuesday, + 2 => Wednesday, + 3 => Thursday, + 4 => Friday, + 5 => Saturday, + val => { + debug_assert!(val == 6); + Sunday + } + } + } + + fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { + match self { + Self::Monday => empty_shrinker(), + _ => single_shrinker(self.previous()), + } + } +} + +impl Arbitrary for Month { + fn arbitrary(g: &mut Gen) -> Self { + use Month::*; + match arbitrary_between!(u8; g, 1, 12) { + 1 => January, + 2 => February, + 3 => March, + 4 => April, + 5 => May, + 6 => June, + 7 => July, + 8 => August, + 9 => September, + 10 => October, + 11 => November, + val => { + debug_assert!(val == 12); + December + } + } + } + + fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { + match self { + Self::January => empty_shrinker(), + _ => single_shrinker(self.previous()), + } + } +} diff --git a/third_party/rust/time/src/rand.rs b/third_party/rust/time/src/rand.rs new file mode 100644 index 0000000000..8afefe5075 --- /dev/null +++ b/third_party/rust/time/src/rand.rs @@ -0,0 +1,99 @@ +//! Implementation of [`Distribution`] for various structs. + +use rand::distributions::{Distribution, Standard}; +use rand::Rng; + +use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; + +impl Distribution<Time> for Standard { + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Time { + Time::__from_hms_nanos_unchecked( + rng.gen_range(0..24), + rng.gen_range(0..60), + rng.gen_range(0..60), + rng.gen_range(0..1_000_000_000), + ) + } +} + +impl Distribution<Date> for Standard { + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Date { + Date::from_julian_day_unchecked( + rng.gen_range(Date::MIN.to_julian_day()..=Date::MAX.to_julian_day()), + ) + } +} + +impl Distribution<UtcOffset> for Standard { + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> UtcOffset { + let seconds = rng.gen_range(-86399..=86399); + UtcOffset::__from_hms_unchecked( + (seconds / 3600) as _, + ((seconds % 3600) / 60) as _, + (seconds % 60) as _, + ) + } +} + +impl Distribution<PrimitiveDateTime> for Standard { + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> PrimitiveDateTime { + PrimitiveDateTime::new(Self.sample(rng), Self.sample(rng)) + } +} + +impl Distribution<OffsetDateTime> for Standard { + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> OffsetDateTime { + let date_time: PrimitiveDateTime = Self.sample(rng); + date_time.assume_offset(Self.sample(rng)) + } +} + +impl Distribution<Duration> for Standard { + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Duration { + Duration::nanoseconds_i128( + rng.gen_range(Duration::MIN.whole_nanoseconds()..=Duration::MAX.whole_nanoseconds()), + ) + } +} + +impl Distribution<Weekday> for Standard { + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Weekday { + use Weekday::*; + + match rng.gen_range(0u8..7) { + 0 => Monday, + 1 => Tuesday, + 2 => Wednesday, + 3 => Thursday, + 4 => Friday, + 5 => Saturday, + val => { + debug_assert!(val == 6); + Sunday + } + } + } +} + +impl Distribution<Month> for Standard { + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Month { + use Month::*; + match rng.gen_range(1u8..=12) { + 1 => January, + 2 => February, + 3 => March, + 4 => April, + 5 => May, + 6 => June, + 7 => July, + 8 => August, + 9 => September, + 10 => October, + 11 => November, + val => { + debug_assert!(val == 12); + December + } + } + } +} diff --git a/third_party/rust/time/src/serde/iso8601.rs b/third_party/rust/time/src/serde/iso8601.rs new file mode 100644 index 0000000000..75deb62f1a --- /dev/null +++ b/third_party/rust/time/src/serde/iso8601.rs @@ -0,0 +1,77 @@ +//! Use the well-known [ISO 8601 format] when serializing and deserializing an [`OffsetDateTime`]. +//! +//! Use this module in combination with serde's [`#[with]`][with] attribute. +//! +//! [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html +//! [with]: https://serde.rs/field-attrs.html#with + +#[cfg(feature = "parsing")] +use core::marker::PhantomData; + +#[cfg(feature = "formatting")] +use serde::ser::Error as _; +#[cfg(feature = "parsing")] +use serde::Deserializer; +#[cfg(feature = "formatting")] +use serde::{Serialize, Serializer}; + +#[cfg(feature = "parsing")] +use super::Visitor; +use crate::format_description::well_known::iso8601::{Config, EncodedConfig}; +use crate::format_description::well_known::Iso8601; +use crate::OffsetDateTime; + +/// The configuration of ISO 8601 used for serde implementations. +pub(crate) const SERDE_CONFIG: EncodedConfig = + Config::DEFAULT.set_year_is_six_digits(true).encode(); + +/// Serialize an [`OffsetDateTime`] using the well-known ISO 8601 format. +#[cfg(feature = "formatting")] +pub fn serialize<S: Serializer>( + datetime: &OffsetDateTime, + serializer: S, +) -> Result<S::Ok, S::Error> { + datetime + .format(&Iso8601::<SERDE_CONFIG>) + .map_err(S::Error::custom)? + .serialize(serializer) +} + +/// Deserialize an [`OffsetDateTime`] from its ISO 8601 representation. +#[cfg(feature = "parsing")] +pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> { + deserializer.deserialize_any(Visitor::<Iso8601<SERDE_CONFIG>>(PhantomData)) +} + +/// Use the well-known ISO 8601 format when serializing and deserializing an +/// [`Option<OffsetDateTime>`]. +/// +/// Use this module in combination with serde's [`#[with]`][with] attribute. +/// +/// [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html +/// [with]: https://serde.rs/field-attrs.html#with +pub mod option { + #[allow(clippy::wildcard_imports)] + use super::*; + + /// Serialize an [`Option<OffsetDateTime>`] using the well-known ISO 8601 format. + #[cfg(feature = "formatting")] + pub fn serialize<S: Serializer>( + option: &Option<OffsetDateTime>, + serializer: S, + ) -> Result<S::Ok, S::Error> { + option + .map(|odt| odt.format(&Iso8601::<SERDE_CONFIG>)) + .transpose() + .map_err(S::Error::custom)? + .serialize(serializer) + } + + /// Deserialize an [`Option<OffsetDateTime>`] from its ISO 8601 representation. + #[cfg(feature = "parsing")] + pub fn deserialize<'a, D: Deserializer<'a>>( + deserializer: D, + ) -> Result<Option<OffsetDateTime>, D::Error> { + deserializer.deserialize_option(Visitor::<Option<Iso8601<SERDE_CONFIG>>>(PhantomData)) + } +} diff --git a/third_party/rust/time/src/serde/mod.rs b/third_party/rust/time/src/serde/mod.rs new file mode 100644 index 0000000000..e9f6d4394c --- /dev/null +++ b/third_party/rust/time/src/serde/mod.rs @@ -0,0 +1,375 @@ +//! Differential formats for serde. +// This also includes the serde implementations for all types. This doesn't need to be externally +// documented, though. + +// Types with guaranteed stable serde representations. Strings are avoided to allow for optimal +// representations in various binary forms. + +/// Consume the next item in a sequence. +macro_rules! item { + ($seq:expr, $name:literal) => { + $seq.next_element()? + .ok_or_else(|| <A::Error as serde::de::Error>::custom(concat!("expected ", $name))) + }; +} + +#[cfg(any(feature = "formatting", feature = "parsing"))] +pub mod iso8601; +#[cfg(any(feature = "formatting", feature = "parsing"))] +pub mod rfc2822; +#[cfg(any(feature = "formatting", feature = "parsing"))] +pub mod rfc3339; +pub mod timestamp; +mod visitor; + +use core::marker::PhantomData; + +#[cfg(feature = "serde-human-readable")] +use serde::ser::Error as _; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +/// Generate a custom serializer and deserializer from the provided string. +/// +/// The syntax accepted by this macro is the same as [`format_description::parse()`], which can +/// be found in [the book](https://time-rs.github.io/book/api/format-description.html). +/// +/// # Usage +/// +/// Invoked as `serde::format_description!(mod_name, Date, "<format string>")`. This puts a +/// module named `mod_name` in the current scope that can be used to format `Date` structs. A +/// submodule (`mod_name::option`) is also generated for `Option<Date>`. Both modules are only +/// visible in the current scope. +/// +/// The returned `Option` will contain a deserialized value if present and `None` if the field +/// is present but the value is `null` (or the equivalent in other formats). To return `None` +/// when the field is not present, you should use `#[serde(default)]` on the field. +/// +/// # Examples +/// +/// ```rust,no_run +/// # use time::OffsetDateTime; +#[cfg_attr( + all(feature = "formatting", feature = "parsing"), + doc = "use ::serde::{Serialize, Deserialize};" +)] +#[cfg_attr( + all(feature = "formatting", not(feature = "parsing")), + doc = "use ::serde::Serialize;" +)] +#[cfg_attr( + all(not(feature = "formatting"), feature = "parsing"), + doc = "use ::serde::Deserialize;" +)] +/// use time::serde; +/// +/// // Makes a module `mod my_format { ... }`. +/// serde::format_description!(my_format, OffsetDateTime, "hour=[hour], minute=[minute]"); +#[cfg_attr( + all(feature = "formatting", feature = "parsing"), + doc = "#[derive(Serialize, Deserialize)]" +)] +#[cfg_attr( + all(feature = "formatting", not(feature = "parsing")), + doc = "#[derive(Serialize)]" +)] +#[cfg_attr( + all(not(feature = "formatting"), feature = "parsing"), + doc = "#[derive(Deserialize)]" +)] +/// # #[allow(dead_code)] +/// struct SerializesWithCustom { +/// #[serde(with = "my_format")] +/// dt: OffsetDateTime, +/// #[serde(with = "my_format::option")] +/// maybe_dt: Option<OffsetDateTime>, +/// } +/// ``` +/// +/// [`format_description::parse()`]: crate::format_description::parse() +#[cfg(all(feature = "macros", any(feature = "formatting", feature = "parsing"),))] +pub use time_macros::serde_format_description as format_description; + +use self::visitor::Visitor; +#[cfg(feature = "parsing")] +use crate::format_description::{modifier, Component, FormatItem}; +use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; + +// region: Date +/// The format used when serializing and deserializing a human-readable `Date`. +#[cfg(feature = "parsing")] +const DATE_FORMAT: &[FormatItem<'_>] = &[ + FormatItem::Component(Component::Year(modifier::Year::default())), + FormatItem::Literal(b"-"), + FormatItem::Component(Component::Month(modifier::Month::default())), + FormatItem::Literal(b"-"), + FormatItem::Component(Component::Day(modifier::Day::default())), +]; + +impl Serialize for Date { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + #[cfg(feature = "serde-human-readable")] + if serializer.is_human_readable() { + return serializer.serialize_str(&match self.format(&DATE_FORMAT) { + Ok(s) => s, + Err(_) => return Err(S::Error::custom("failed formatting `Date`")), + }); + } + + (self.year(), self.ordinal()).serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for Date { + fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> { + if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() { + deserializer.deserialize_any(Visitor::<Self>(PhantomData)) + } else { + deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData)) + } + } +} +// endregion date + +// region: Duration +impl Serialize for Duration { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + #[cfg(feature = "serde-human-readable")] + if serializer.is_human_readable() { + return serializer.collect_str(&format_args!( + "{}.{:>09}", + self.whole_seconds(), + self.subsec_nanoseconds().abs() + )); + } + + (self.whole_seconds(), self.subsec_nanoseconds()).serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for Duration { + fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> { + if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() { + deserializer.deserialize_any(Visitor::<Self>(PhantomData)) + } else { + deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData)) + } + } +} +// endregion Duration + +// region: OffsetDateTime +/// The format used when serializing and deserializing a human-readable `OffsetDateTime`. +#[cfg(feature = "parsing")] +const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = &[ + FormatItem::Compound(DATE_FORMAT), + FormatItem::Literal(b" "), + FormatItem::Compound(TIME_FORMAT), + FormatItem::Literal(b" "), + FormatItem::Compound(UTC_OFFSET_FORMAT), +]; + +impl Serialize for OffsetDateTime { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + #[cfg(feature = "serde-human-readable")] + if serializer.is_human_readable() { + return serializer.serialize_str(&match self.format(&OFFSET_DATE_TIME_FORMAT) { + Ok(s) => s, + Err(_) => return Err(S::Error::custom("failed formatting `OffsetDateTime`")), + }); + } + + ( + self.year(), + self.ordinal(), + self.hour(), + self.minute(), + self.second(), + self.nanosecond(), + self.offset.whole_hours(), + self.offset.minutes_past_hour(), + self.offset.seconds_past_minute(), + ) + .serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for OffsetDateTime { + fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> { + if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() { + deserializer.deserialize_any(Visitor::<Self>(PhantomData)) + } else { + deserializer.deserialize_tuple(9, Visitor::<Self>(PhantomData)) + } + } +} +// endregion OffsetDateTime + +// region: PrimitiveDateTime +/// The format used when serializing and deserializing a human-readable `PrimitiveDateTime`. +#[cfg(feature = "parsing")] +const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = &[ + FormatItem::Compound(DATE_FORMAT), + FormatItem::Literal(b" "), + FormatItem::Compound(TIME_FORMAT), +]; + +impl Serialize for PrimitiveDateTime { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + #[cfg(feature = "serde-human-readable")] + if serializer.is_human_readable() { + return serializer.serialize_str(&match self.format(&PRIMITIVE_DATE_TIME_FORMAT) { + Ok(s) => s, + Err(_) => return Err(<S::Error>::custom("failed formatting `PrimitiveDateTime`")), + }); + } + + ( + self.year(), + self.ordinal(), + self.hour(), + self.minute(), + self.second(), + self.nanosecond(), + ) + .serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for PrimitiveDateTime { + fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> { + if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() { + deserializer.deserialize_any(Visitor::<Self>(PhantomData)) + } else { + deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData)) + } + } +} +// endregion PrimitiveDateTime + +// region: Time +/// The format used when serializing and deserializing a human-readable `Time`. +#[cfg(feature = "parsing")] +const TIME_FORMAT: &[FormatItem<'_>] = &[ + FormatItem::Component(Component::Hour(<modifier::Hour>::default())), + FormatItem::Literal(b":"), + FormatItem::Component(Component::Minute(<modifier::Minute>::default())), + FormatItem::Literal(b":"), + FormatItem::Component(Component::Second(<modifier::Second>::default())), + FormatItem::Literal(b"."), + FormatItem::Component(Component::Subsecond(<modifier::Subsecond>::default())), +]; + +impl Serialize for Time { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + #[cfg(feature = "serde-human-readable")] + if serializer.is_human_readable() { + return serializer.serialize_str(&match self.format(&TIME_FORMAT) { + Ok(s) => s, + Err(_) => return Err(S::Error::custom("failed formatting `Time`")), + }); + } + + (self.hour(), self.minute(), self.second(), self.nanosecond()).serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for Time { + fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> { + if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() { + deserializer.deserialize_any(Visitor::<Self>(PhantomData)) + } else { + deserializer.deserialize_tuple(4, Visitor::<Self>(PhantomData)) + } + } +} +// endregion Time + +// region: UtcOffset +/// The format used when serializing and deserializing a human-readable `UtcOffset`. +#[cfg(feature = "parsing")] +const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = &[ + FormatItem::Component(Component::OffsetHour(modifier::OffsetHour::default())), + FormatItem::Literal(b":"), + FormatItem::Component(Component::OffsetMinute(modifier::OffsetMinute::default())), + FormatItem::Literal(b":"), + FormatItem::Component(Component::OffsetSecond(modifier::OffsetSecond::default())), +]; + +impl Serialize for UtcOffset { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + #[cfg(feature = "serde-human-readable")] + if serializer.is_human_readable() { + return serializer.serialize_str(&match self.format(&UTC_OFFSET_FORMAT) { + Ok(s) => s, + Err(_) => return Err(S::Error::custom("failed formatting `UtcOffset`")), + }); + } + + ( + self.whole_hours(), + self.minutes_past_hour(), + self.seconds_past_minute(), + ) + .serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for UtcOffset { + fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> { + if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() { + deserializer.deserialize_any(Visitor::<Self>(PhantomData)) + } else { + deserializer.deserialize_tuple(3, Visitor::<Self>(PhantomData)) + } + } +} +// endregion UtcOffset + +// region: Weekday +impl Serialize for Weekday { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + #[cfg(feature = "serde-human-readable")] + if serializer.is_human_readable() { + #[cfg(not(feature = "std"))] + use alloc::string::ToString; + return self.to_string().serialize(serializer); + } + + self.number_from_monday().serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for Weekday { + fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> { + if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() { + deserializer.deserialize_any(Visitor::<Self>(PhantomData)) + } else { + deserializer.deserialize_u8(Visitor::<Self>(PhantomData)) + } + } +} +// endregion Weekday + +// region: Month +impl Serialize for Month { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + #[cfg(feature = "serde-human-readable")] + if serializer.is_human_readable() { + #[cfg(not(feature = "std"))] + use alloc::string::String; + return self.to_string().serialize(serializer); + } + + (*self as u8).serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for Month { + fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> { + if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() { + deserializer.deserialize_any(Visitor::<Self>(PhantomData)) + } else { + deserializer.deserialize_u8(Visitor::<Self>(PhantomData)) + } + } +} +// endregion Month diff --git a/third_party/rust/time/src/serde/rfc2822.rs b/third_party/rust/time/src/serde/rfc2822.rs new file mode 100644 index 0000000000..eca90f5204 --- /dev/null +++ b/third_party/rust/time/src/serde/rfc2822.rs @@ -0,0 +1,72 @@ +//! Use the well-known [RFC2822 format] when serializing and deserializing an [`OffsetDateTime`]. +//! +//! Use this module in combination with serde's [`#[with]`][with] attribute. +//! +//! [RFC2822 format]: https://tools.ietf.org/html/rfc2822#section-3.3 +//! [with]: https://serde.rs/field-attrs.html#with + +#[cfg(feature = "parsing")] +use core::marker::PhantomData; + +#[cfg(feature = "formatting")] +use serde::ser::Error as _; +#[cfg(feature = "parsing")] +use serde::Deserializer; +#[cfg(feature = "formatting")] +use serde::{Serialize, Serializer}; + +#[cfg(feature = "parsing")] +use super::Visitor; +use crate::format_description::well_known::Rfc2822; +use crate::OffsetDateTime; + +/// Serialize an [`OffsetDateTime`] using the well-known RFC2822 format. +#[cfg(feature = "formatting")] +pub fn serialize<S: Serializer>( + datetime: &OffsetDateTime, + serializer: S, +) -> Result<S::Ok, S::Error> { + datetime + .format(&Rfc2822) + .map_err(S::Error::custom)? + .serialize(serializer) +} + +/// Deserialize an [`OffsetDateTime`] from its RFC2822 representation. +#[cfg(feature = "parsing")] +pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> { + deserializer.deserialize_str(Visitor::<Rfc2822>(PhantomData)) +} + +/// Use the well-known [RFC2822 format] when serializing and deserializing an +/// [`Option<OffsetDateTime>`]. +/// +/// Use this module in combination with serde's [`#[with]`][with] attribute. +/// +/// [RFC2822 format]: https://tools.ietf.org/html/rfc2822#section-3.3 +/// [with]: https://serde.rs/field-attrs.html#with +pub mod option { + #[allow(clippy::wildcard_imports)] + use super::*; + + /// Serialize an [`Option<OffsetDateTime>`] using the well-known RFC2822 format. + #[cfg(feature = "formatting")] + pub fn serialize<S: Serializer>( + option: &Option<OffsetDateTime>, + serializer: S, + ) -> Result<S::Ok, S::Error> { + option + .map(|odt| odt.format(&Rfc2822)) + .transpose() + .map_err(S::Error::custom)? + .serialize(serializer) + } + + /// Deserialize an [`Option<OffsetDateTime>`] from its RFC2822 representation. + #[cfg(feature = "parsing")] + pub fn deserialize<'a, D: Deserializer<'a>>( + deserializer: D, + ) -> Result<Option<OffsetDateTime>, D::Error> { + deserializer.deserialize_option(Visitor::<Option<Rfc2822>>(PhantomData)) + } +} diff --git a/third_party/rust/time/src/serde/rfc3339.rs b/third_party/rust/time/src/serde/rfc3339.rs new file mode 100644 index 0000000000..b1ffb25130 --- /dev/null +++ b/third_party/rust/time/src/serde/rfc3339.rs @@ -0,0 +1,72 @@ +//! Use the well-known [RFC3339 format] when serializing and deserializing an [`OffsetDateTime`]. +//! +//! Use this module in combination with serde's [`#[with]`][with] attribute. +//! +//! [RFC3339 format]: https://tools.ietf.org/html/rfc3339#section-5.6 +//! [with]: https://serde.rs/field-attrs.html#with + +#[cfg(feature = "parsing")] +use core::marker::PhantomData; + +#[cfg(feature = "formatting")] +use serde::ser::Error as _; +#[cfg(feature = "parsing")] +use serde::Deserializer; +#[cfg(feature = "formatting")] +use serde::{Serialize, Serializer}; + +#[cfg(feature = "parsing")] +use super::Visitor; +use crate::format_description::well_known::Rfc3339; +use crate::OffsetDateTime; + +/// Serialize an [`OffsetDateTime`] using the well-known RFC3339 format. +#[cfg(feature = "formatting")] +pub fn serialize<S: Serializer>( + datetime: &OffsetDateTime, + serializer: S, +) -> Result<S::Ok, S::Error> { + datetime + .format(&Rfc3339) + .map_err(S::Error::custom)? + .serialize(serializer) +} + +/// Deserialize an [`OffsetDateTime`] from its RFC3339 representation. +#[cfg(feature = "parsing")] +pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> { + deserializer.deserialize_str(Visitor::<Rfc3339>(PhantomData)) +} + +/// Use the well-known [RFC3339 format] when serializing and deserializing an +/// [`Option<OffsetDateTime>`]. +/// +/// Use this module in combination with serde's [`#[with]`][with] attribute. +/// +/// [RFC3339 format]: https://tools.ietf.org/html/rfc3339#section-5.6 +/// [with]: https://serde.rs/field-attrs.html#with +pub mod option { + #[allow(clippy::wildcard_imports)] + use super::*; + + /// Serialize an [`Option<OffsetDateTime>`] using the well-known RFC3339 format. + #[cfg(feature = "formatting")] + pub fn serialize<S: Serializer>( + option: &Option<OffsetDateTime>, + serializer: S, + ) -> Result<S::Ok, S::Error> { + option + .map(|odt| odt.format(&Rfc3339)) + .transpose() + .map_err(S::Error::custom)? + .serialize(serializer) + } + + /// Deserialize an [`Option<OffsetDateTime>`] from its RFC3339 representation. + #[cfg(feature = "parsing")] + pub fn deserialize<'a, D: Deserializer<'a>>( + deserializer: D, + ) -> Result<Option<OffsetDateTime>, D::Error> { + deserializer.deserialize_option(Visitor::<Option<Rfc3339>>(PhantomData)) + } +} diff --git a/third_party/rust/time/src/serde/timestamp.rs b/third_party/rust/time/src/serde/timestamp.rs new file mode 100644 index 0000000000..d86e6b9336 --- /dev/null +++ b/third_party/rust/time/src/serde/timestamp.rs @@ -0,0 +1,60 @@ +//! Treat an [`OffsetDateTime`] as a [Unix timestamp] for the purposes of serde. +//! +//! Use this module in combination with serde's [`#[with]`][with] attribute. +//! +//! When deserializing, the offset is assumed to be UTC. +//! +//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time +//! [with]: https://serde.rs/field-attrs.html#with + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::OffsetDateTime; + +/// Serialize an `OffsetDateTime` as its Unix timestamp +pub fn serialize<S: Serializer>( + datetime: &OffsetDateTime, + serializer: S, +) -> Result<S::Ok, S::Error> { + datetime.unix_timestamp().serialize(serializer) +} + +/// Deserialize an `OffsetDateTime` from its Unix timestamp +pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> { + OffsetDateTime::from_unix_timestamp(<_>::deserialize(deserializer)?) + .map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err)) +} + +/// Treat an `Option<OffsetDateTime>` as a [Unix timestamp] for the purposes of +/// serde. +/// +/// Use this module in combination with serde's [`#[with]`][with] attribute. +/// +/// When deserializing, the offset is assumed to be UTC. +/// +/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time +/// [with]: https://serde.rs/field-attrs.html#with +pub mod option { + #[allow(clippy::wildcard_imports)] + use super::*; + + /// Serialize an `Option<OffsetDateTime>` as its Unix timestamp + pub fn serialize<S: Serializer>( + option: &Option<OffsetDateTime>, + serializer: S, + ) -> Result<S::Ok, S::Error> { + option + .map(OffsetDateTime::unix_timestamp) + .serialize(serializer) + } + + /// Deserialize an `Option<OffsetDateTime>` from its Unix timestamp + pub fn deserialize<'a, D: Deserializer<'a>>( + deserializer: D, + ) -> Result<Option<OffsetDateTime>, D::Error> { + Option::deserialize(deserializer)? + .map(OffsetDateTime::from_unix_timestamp) + .transpose() + .map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err)) + } +} diff --git a/third_party/rust/time/src/serde/visitor.rs b/third_party/rust/time/src/serde/visitor.rs new file mode 100644 index 0000000000..e61989afde --- /dev/null +++ b/third_party/rust/time/src/serde/visitor.rs @@ -0,0 +1,316 @@ +//! Serde visitor for various types. + +use core::fmt; +use core::marker::PhantomData; + +use serde::de; +#[cfg(feature = "parsing")] +use serde::Deserializer; + +#[cfg(feature = "parsing")] +use super::{ + DATE_FORMAT, OFFSET_DATE_TIME_FORMAT, PRIMITIVE_DATE_TIME_FORMAT, TIME_FORMAT, + UTC_OFFSET_FORMAT, +}; +use crate::error::ComponentRange; +#[cfg(feature = "parsing")] +use crate::format_description::well_known::*; +use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; + +/// A serde visitor for various types. +pub(super) struct Visitor<T: ?Sized>(pub(super) PhantomData<T>); + +impl<'a> de::Visitor<'a> for Visitor<Date> { + type Value = Date; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a `Date`") + } + + #[cfg(feature = "parsing")] + fn visit_str<E: de::Error>(self, value: &str) -> Result<Date, E> { + Date::parse(value, &DATE_FORMAT).map_err(E::custom) + } + + fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<Date, A::Error> { + let year = item!(seq, "year")?; + let ordinal = item!(seq, "day of year")?; + Date::from_ordinal_date(year, ordinal).map_err(ComponentRange::into_de_error) + } +} + +impl<'a> de::Visitor<'a> for Visitor<Duration> { + type Value = Duration; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a `Duration`") + } + + fn visit_str<E: de::Error>(self, value: &str) -> Result<Duration, E> { + let (seconds, nanoseconds) = value.split_once('.').ok_or_else(|| { + de::Error::invalid_value(de::Unexpected::Str(value), &"a decimal point") + })?; + + let seconds = seconds + .parse() + .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(seconds), &"seconds"))?; + let mut nanoseconds = nanoseconds.parse().map_err(|_| { + de::Error::invalid_value(de::Unexpected::Str(nanoseconds), &"nanoseconds") + })?; + + if seconds < 0 { + nanoseconds *= -1; + } + + Ok(Duration::new(seconds, nanoseconds)) + } + + fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<Duration, A::Error> { + let seconds = item!(seq, "seconds")?; + let nanoseconds = item!(seq, "nanoseconds")?; + Ok(Duration::new(seconds, nanoseconds)) + } +} + +impl<'a> de::Visitor<'a> for Visitor<OffsetDateTime> { + type Value = OffsetDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("an `OffsetDateTime`") + } + + #[cfg(feature = "parsing")] + fn visit_str<E: de::Error>(self, value: &str) -> Result<OffsetDateTime, E> { + OffsetDateTime::parse(value, &OFFSET_DATE_TIME_FORMAT).map_err(E::custom) + } + + fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<OffsetDateTime, A::Error> { + let year = item!(seq, "year")?; + let ordinal = item!(seq, "day of year")?; + let hour = item!(seq, "hour")?; + let minute = item!(seq, "minute")?; + let second = item!(seq, "second")?; + let nanosecond = item!(seq, "nanosecond")?; + let offset_hours = item!(seq, "offset hours")?; + let offset_minutes = item!(seq, "offset minutes")?; + let offset_seconds = item!(seq, "offset seconds")?; + + Date::from_ordinal_date(year, ordinal) + .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond)) + .and_then(|datetime| { + UtcOffset::from_hms(offset_hours, offset_minutes, offset_seconds) + .map(|offset| datetime.assume_offset(offset)) + }) + .map_err(ComponentRange::into_de_error) + } +} + +impl<'a> de::Visitor<'a> for Visitor<PrimitiveDateTime> { + type Value = PrimitiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a `PrimitiveDateTime`") + } + + #[cfg(feature = "parsing")] + fn visit_str<E: de::Error>(self, value: &str) -> Result<PrimitiveDateTime, E> { + PrimitiveDateTime::parse(value, &PRIMITIVE_DATE_TIME_FORMAT).map_err(E::custom) + } + + fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<PrimitiveDateTime, A::Error> { + let year = item!(seq, "year")?; + let ordinal = item!(seq, "day of year")?; + let hour = item!(seq, "hour")?; + let minute = item!(seq, "minute")?; + let second = item!(seq, "second")?; + let nanosecond = item!(seq, "nanosecond")?; + + Date::from_ordinal_date(year, ordinal) + .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond)) + .map_err(ComponentRange::into_de_error) + } +} + +impl<'a> de::Visitor<'a> for Visitor<Time> { + type Value = Time; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a `Time`") + } + + #[cfg(feature = "parsing")] + fn visit_str<E: de::Error>(self, value: &str) -> Result<Time, E> { + Time::parse(value, &TIME_FORMAT).map_err(E::custom) + } + + fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<Time, A::Error> { + let hour = item!(seq, "hour")?; + let minute = item!(seq, "minute")?; + let second = item!(seq, "second")?; + let nanosecond = item!(seq, "nanosecond")?; + + Time::from_hms_nano(hour, minute, second, nanosecond).map_err(ComponentRange::into_de_error) + } +} + +impl<'a> de::Visitor<'a> for Visitor<UtcOffset> { + type Value = UtcOffset; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a `UtcOffset`") + } + + #[cfg(feature = "parsing")] + fn visit_str<E: de::Error>(self, value: &str) -> Result<UtcOffset, E> { + UtcOffset::parse(value, &UTC_OFFSET_FORMAT).map_err(E::custom) + } + + fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<UtcOffset, A::Error> { + let hours = item!(seq, "offset hours")?; + let minutes = item!(seq, "offset minutes")?; + let seconds = item!(seq, "offset seconds")?; + + UtcOffset::from_hms(hours, minutes, seconds).map_err(ComponentRange::into_de_error) + } +} + +impl<'a> de::Visitor<'a> for Visitor<Weekday> { + type Value = Weekday; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a `Weekday`") + } + + fn visit_str<E: de::Error>(self, value: &str) -> Result<Weekday, E> { + match value { + "Monday" => Ok(Weekday::Monday), + "Tuesday" => Ok(Weekday::Tuesday), + "Wednesday" => Ok(Weekday::Wednesday), + "Thursday" => Ok(Weekday::Thursday), + "Friday" => Ok(Weekday::Friday), + "Saturday" => Ok(Weekday::Saturday), + "Sunday" => Ok(Weekday::Sunday), + _ => Err(E::invalid_value(de::Unexpected::Str(value), &"a `Weekday`")), + } + } + + fn visit_u64<E: de::Error>(self, value: u64) -> Result<Weekday, E> { + match value { + 1 => Ok(Weekday::Monday), + 2 => Ok(Weekday::Tuesday), + 3 => Ok(Weekday::Wednesday), + 4 => Ok(Weekday::Thursday), + 5 => Ok(Weekday::Friday), + 6 => Ok(Weekday::Saturday), + 7 => Ok(Weekday::Sunday), + _ => Err(E::invalid_value( + de::Unexpected::Unsigned(value), + &"a value in the range 1..=7", + )), + } + } +} + +impl<'a> de::Visitor<'a> for Visitor<Month> { + type Value = Month; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a `Month`") + } + + fn visit_str<E: de::Error>(self, value: &str) -> Result<Month, E> { + match value { + "January" => Ok(Month::January), + "February" => Ok(Month::February), + "March" => Ok(Month::March), + "April" => Ok(Month::April), + "May" => Ok(Month::May), + "June" => Ok(Month::June), + "July" => Ok(Month::July), + "August" => Ok(Month::August), + "September" => Ok(Month::September), + "October" => Ok(Month::October), + "November" => Ok(Month::November), + "December" => Ok(Month::December), + _ => Err(E::invalid_value(de::Unexpected::Str(value), &"a `Month`")), + } + } + + fn visit_u64<E: de::Error>(self, value: u64) -> Result<Month, E> { + match value { + 1 => Ok(Month::January), + 2 => Ok(Month::February), + 3 => Ok(Month::March), + 4 => Ok(Month::April), + 5 => Ok(Month::May), + 6 => Ok(Month::June), + 7 => Ok(Month::July), + 8 => Ok(Month::August), + 9 => Ok(Month::September), + 10 => Ok(Month::October), + 11 => Ok(Month::November), + 12 => Ok(Month::December), + _ => Err(E::invalid_value( + de::Unexpected::Unsigned(value), + &"a value in the range 1..=12", + )), + } + } +} + +/// Implement a visitor for a well-known format. +macro_rules! well_known { + ($article:literal, $name:literal, $($ty:tt)+) => { + #[cfg(feature = "parsing")] + impl<'a> de::Visitor<'a> for Visitor<$($ty)+> { + type Value = OffsetDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(concat!($article, " ", $name, "-formatted `OffsetDateTime`")) + } + + fn visit_str<E: de::Error>(self, value: &str) -> Result<OffsetDateTime, E> { + OffsetDateTime::parse(value, &$($ty)+).map_err(E::custom) + } + } + + #[cfg(feature = "parsing")] + impl<'a> de::Visitor<'a> for Visitor<Option<$($ty)+>> { + type Value = Option<OffsetDateTime>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(concat!( + $article, + " ", + $name, + "-formatted `Option<OffsetDateTime>`" + )) + } + + fn visit_some<D: Deserializer<'a>>( + self, + deserializer: D, + ) -> Result<Option<OffsetDateTime>, D::Error> { + deserializer + .deserialize_any(Visitor::<$($ty)+>(PhantomData)) + .map(Some) + } + + fn visit_none<E: de::Error>(self) -> Result<Option<OffsetDateTime>, E> { + Ok(None) + } + + fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> { + Ok(None) + } + } + }; +} + +well_known!("an", "RFC2822", Rfc2822); +well_known!("an", "RFC3339", Rfc3339); +well_known!( + "an", + "ISO 8601", + Iso8601::<{ super::iso8601::SERDE_CONFIG }> +); diff --git a/third_party/rust/time/src/sys/local_offset_at/imp.rs b/third_party/rust/time/src/sys/local_offset_at/imp.rs new file mode 100644 index 0000000000..251fa667c2 --- /dev/null +++ b/third_party/rust/time/src/sys/local_offset_at/imp.rs @@ -0,0 +1,8 @@ +//! A fallback for any OS not covered. + +use crate::{OffsetDateTime, UtcOffset}; + +#[allow(clippy::missing_docs_in_private_items)] +pub(super) fn local_offset_at(_datetime: OffsetDateTime) -> Option<UtcOffset> { + None +} diff --git a/third_party/rust/time/src/sys/local_offset_at/mod.rs b/third_party/rust/time/src/sys/local_offset_at/mod.rs new file mode 100644 index 0000000000..f0bc4be3cc --- /dev/null +++ b/third_party/rust/time/src/sys/local_offset_at/mod.rs @@ -0,0 +1,23 @@ +//! A method to obtain the local offset from UTC. + +#![allow(clippy::missing_const_for_fn)] + +#[cfg_attr(target_family = "windows", path = "windows.rs")] +#[cfg_attr(target_family = "unix", path = "unix.rs")] +#[cfg_attr( + all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")), + feature = "wasm-bindgen" + ), + path = "wasm_js.rs" +)] +mod imp; + +use crate::{OffsetDateTime, UtcOffset}; + +/// Attempt to obtain the system's UTC offset. If the offset cannot be determined, `None` is +/// returned. +pub(crate) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { + imp::local_offset_at(datetime) +} diff --git a/third_party/rust/time/src/sys/local_offset_at/unix.rs b/third_party/rust/time/src/sys/local_offset_at/unix.rs new file mode 100644 index 0000000000..6e849892da --- /dev/null +++ b/third_party/rust/time/src/sys/local_offset_at/unix.rs @@ -0,0 +1,169 @@ +//! Get the system's UTC offset on Unix. + +use core::mem::MaybeUninit; + +use crate::{OffsetDateTime, UtcOffset}; + +/// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on any error. +/// +/// # Safety +/// +/// This method must only be called when the process is single-threaded. +/// +/// This method will remain `unsafe` until `std::env::set_var` is deprecated or has its behavior +/// altered. This method is, on its own, safe. It is the presence of a safe, unsound way to set +/// environment variables that makes it unsafe. +unsafe fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> { + extern "C" { + #[cfg_attr(target_os = "netbsd", link_name = "__tzset50")] + fn tzset(); + } + + // The exact type of `timestamp` beforehand can vary, so this conversion is necessary. + #[allow(clippy::useless_conversion)] + let timestamp = timestamp.try_into().ok()?; + + let mut tm = MaybeUninit::uninit(); + + // Update timezone information from system. `localtime_r` does not do this for us. + // + // Safety: tzset is thread-safe. + unsafe { tzset() }; + + // Safety: We are calling a system API, which mutates the `tm` variable. If a null + // pointer is returned, an error occurred. + let tm_ptr = unsafe { libc::localtime_r(×tamp, tm.as_mut_ptr()) }; + + if tm_ptr.is_null() { + None + } else { + // Safety: The value was initialized, as we no longer have a null pointer. + Some(unsafe { tm.assume_init() }) + } +} + +/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. +// This is available to any target known to have the `tm_gmtoff` extension. +#[cfg(any( + target_os = "redox", + target_os = "linux", + target_os = "l4re", + target_os = "android", + target_os = "emscripten", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd", + target_os = "haiku", +))] +fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> { + let seconds: i32 = tm.tm_gmtoff.try_into().ok()?; + UtcOffset::from_hms( + (seconds / 3_600) as _, + ((seconds / 60) % 60) as _, + (seconds % 60) as _, + ) + .ok() +} + +/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. +#[cfg(all( + not(unsound_local_offset), + not(any( + target_os = "redox", + target_os = "linux", + target_os = "l4re", + target_os = "android", + target_os = "emscripten", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd", + target_os = "haiku", + )) +))] +#[allow(unused_variables, clippy::missing_const_for_fn)] +fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> { + None +} + +/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. +// This method can return an incorrect value, as it only approximates the `tm_gmtoff` field. As such +// it is gated behind `--cfg unsound_local_offset`. The reason it can return an incorrect value is +// that daylight saving time does not start on the same date every year, nor are the rules for +// daylight saving time the same for every year. This implementation assumes 1970 is equivalent to +// every other year, which is not always the case. +#[cfg(all( + unsound_local_offset, + not(any( + target_os = "redox", + target_os = "linux", + target_os = "l4re", + target_os = "android", + target_os = "emscripten", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd", + target_os = "haiku", + )) +))] +fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> { + use crate::Date; + + let mut tm = tm; + if tm.tm_sec == 60 { + // Leap seconds are not currently supported. + tm.tm_sec = 59; + } + + let local_timestamp = + Date::from_ordinal_date(1900 + tm.tm_year, u16::try_from(tm.tm_yday).ok()? + 1) + .ok()? + .with_hms( + tm.tm_hour.try_into().ok()?, + tm.tm_min.try_into().ok()?, + tm.tm_sec.try_into().ok()?, + ) + .ok()? + .assume_utc() + .unix_timestamp(); + + let diff_secs: i32 = (local_timestamp - datetime.unix_timestamp()) + .try_into() + .ok()?; + + UtcOffset::from_hms( + (diff_secs / 3_600) as _, + ((diff_secs / 60) % 60) as _, + (diff_secs % 60) as _, + ) + .ok() +} + +/// Obtain the system's UTC offset. +pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { + // Ensure that the process is single-threaded unless the user has explicitly opted out of this + // check. This is to prevent issues with the environment being mutated by a different thread in + // the process while execution of this function is taking place, which can cause a segmentation + // fault by dereferencing a dangling pointer. + // If the `num_threads` crate is incapable of determining the number of running threads, then + // we conservatively return `None` to avoid a soundness bug. + if !cfg!(unsound_local_offset) && num_threads::is_single_threaded() != Some(true) { + return None; + } + + // Safety: We have just confirmed that the process is single-threaded or the user has explicitly + // opted out of soundness. + let tm = unsafe { timestamp_to_tm(datetime.unix_timestamp()) }?; + tm_to_offset(tm) +} diff --git a/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs b/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs new file mode 100644 index 0000000000..fcea4b0f5a --- /dev/null +++ b/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs @@ -0,0 +1,12 @@ +use crate::{OffsetDateTime, UtcOffset}; + +/// Obtain the system's UTC offset. +pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { + let js_date: js_sys::Date = datetime.into(); + // The number of minutes returned by getTimezoneOffset() is positive if the local time zone + // is behind UTC, and negative if the local time zone is ahead of UTC. For example, + // for UTC+10, -600 will be returned. + let timezone_offset = (js_date.get_timezone_offset() as i32) * -60; + + UtcOffset::from_whole_seconds(timezone_offset).ok() +} diff --git a/third_party/rust/time/src/sys/local_offset_at/windows.rs b/third_party/rust/time/src/sys/local_offset_at/windows.rs new file mode 100644 index 0000000000..fed01bf3a9 --- /dev/null +++ b/third_party/rust/time/src/sys/local_offset_at/windows.rs @@ -0,0 +1,114 @@ +//! Get the system's UTC offset on Windows. + +use core::mem::MaybeUninit; + +use crate::{OffsetDateTime, UtcOffset}; + +// ffi: WINAPI FILETIME struct +#[repr(C)] +#[allow(non_snake_case, clippy::missing_docs_in_private_items)] +struct FileTime { + dwLowDateTime: u32, + dwHighDateTime: u32, +} + +// ffi: WINAPI SYSTEMTIME struct +#[repr(C)] +#[allow(non_snake_case, clippy::missing_docs_in_private_items)] +struct SystemTime { + wYear: u16, + wMonth: u16, + wDayOfWeek: u16, + wDay: u16, + wHour: u16, + wMinute: u16, + wSecond: u16, + wMilliseconds: u16, +} + +#[link(name = "kernel32")] +extern "system" { + // https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime + fn SystemTimeToFileTime(lpSystemTime: *const SystemTime, lpFileTime: *mut FileTime) -> i32; + + // https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime + fn SystemTimeToTzSpecificLocalTime( + lpTimeZoneInformation: *const core::ffi::c_void, // We only pass `nullptr` here + lpUniversalTime: *const SystemTime, + lpLocalTime: *mut SystemTime, + ) -> i32; +} + +/// Convert a `SYSTEMTIME` to a `FILETIME`. Returns `None` if any error occurred. +fn systemtime_to_filetime(systime: &SystemTime) -> Option<FileTime> { + let mut ft = MaybeUninit::uninit(); + + // Safety: `SystemTimeToFileTime` is thread-safe. + if 0 == unsafe { SystemTimeToFileTime(systime, ft.as_mut_ptr()) } { + // failed + None + } else { + // Safety: The call succeeded. + Some(unsafe { ft.assume_init() }) + } +} + +/// Convert a `FILETIME` to an `i64`, representing a number of seconds. +fn filetime_to_secs(filetime: &FileTime) -> i64 { + /// FILETIME represents 100-nanosecond intervals + const FT_TO_SECS: i64 = 10_000_000; + ((filetime.dwHighDateTime as i64) << 32 | filetime.dwLowDateTime as i64) / FT_TO_SECS +} + +/// Convert an [`OffsetDateTime`] to a `SYSTEMTIME`. +fn offset_to_systemtime(datetime: OffsetDateTime) -> SystemTime { + let (_, month, day_of_month) = datetime.to_offset(UtcOffset::UTC).date().to_calendar_date(); + SystemTime { + wYear: datetime.year() as _, + wMonth: month as _, + wDay: day_of_month as _, + wDayOfWeek: 0, // ignored + wHour: datetime.hour() as _, + wMinute: datetime.minute() as _, + wSecond: datetime.second() as _, + wMilliseconds: datetime.millisecond(), + } +} + +/// Obtain the system's UTC offset. +pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { + // This function falls back to UTC if any system call fails. + let systime_utc = offset_to_systemtime(datetime.to_offset(UtcOffset::UTC)); + + // Safety: `local_time` is only read if it is properly initialized, and + // `SystemTimeToTzSpecificLocalTime` is thread-safe. + let systime_local = unsafe { + let mut local_time = MaybeUninit::uninit(); + + if 0 == SystemTimeToTzSpecificLocalTime( + core::ptr::null(), // use system's current timezone + &systime_utc, + local_time.as_mut_ptr(), + ) { + // call failed + return None; + } else { + local_time.assume_init() + } + }; + + // Convert SYSTEMTIMEs to FILETIMEs so we can perform arithmetic on them. + let ft_system = systemtime_to_filetime(&systime_utc)?; + let ft_local = systemtime_to_filetime(&systime_local)?; + + let diff_secs: i32 = (filetime_to_secs(&ft_local) - filetime_to_secs(&ft_system)) + .try_into() + .ok()?; + + UtcOffset::from_hms( + (diff_secs / 3_600) as _, + ((diff_secs / 60) % 60) as _, + (diff_secs % 60) as _, + ) + .ok() +} diff --git a/third_party/rust/time/src/sys/mod.rs b/third_party/rust/time/src/sys/mod.rs new file mode 100644 index 0000000000..90bbf7ff3b --- /dev/null +++ b/third_party/rust/time/src/sys/mod.rs @@ -0,0 +1,9 @@ +//! Functions with a common interface that rely on system calls. + +#![allow(unsafe_code)] // We're interfacing with system calls. + +#[cfg(feature = "local-offset")] +mod local_offset_at; + +#[cfg(feature = "local-offset")] +pub(crate) use local_offset_at::local_offset_at; diff --git a/third_party/rust/time/src/tests.rs b/third_party/rust/time/src/tests.rs new file mode 100644 index 0000000000..66c977d91d --- /dev/null +++ b/third_party/rust/time/src/tests.rs @@ -0,0 +1,113 @@ +#![cfg(all( // require `--all-features` to be passed + feature = "default", + feature = "alloc", + feature = "formatting", + feature = "large-dates", + feature = "local-offset", + feature = "macros", + feature = "parsing", + feature = "quickcheck", + feature = "serde-human-readable", + feature = "serde-well-known", + feature = "std", + feature = "rand", + feature = "serde", +))] +#![allow( + clippy::let_underscore_drop, + clippy::clone_on_copy, + clippy::cognitive_complexity, + clippy::std_instead_of_core +)] + +//! Tests for internal details. +//! +//! This module should only be used when it is not possible to test the implementation in a +//! reasonable manner externally. + +use std::num::NonZeroU8; + +use crate::formatting::DigitCount; +use crate::parsing::combinator::rfc::iso8601; +use crate::parsing::shim::Integer; +use crate::{duration, parsing}; + +#[test] +fn digit_count() { + assert_eq!(1_u8.num_digits(), 1); + assert_eq!(9_u8.num_digits(), 1); + assert_eq!(10_u8.num_digits(), 2); + assert_eq!(99_u8.num_digits(), 2); + assert_eq!(100_u8.num_digits(), 3); + + assert_eq!(1_u16.num_digits(), 1); + assert_eq!(9_u16.num_digits(), 1); + assert_eq!(10_u16.num_digits(), 2); + assert_eq!(99_u16.num_digits(), 2); + assert_eq!(100_u16.num_digits(), 3); + assert_eq!(999_u16.num_digits(), 3); + assert_eq!(1_000_u16.num_digits(), 4); + assert_eq!(9_999_u16.num_digits(), 4); + assert_eq!(10_000_u16.num_digits(), 5); + + assert_eq!(1_u32.num_digits(), 1); + assert_eq!(9_u32.num_digits(), 1); + assert_eq!(10_u32.num_digits(), 2); + assert_eq!(99_u32.num_digits(), 2); + assert_eq!(100_u32.num_digits(), 3); + assert_eq!(999_u32.num_digits(), 3); + assert_eq!(1_000_u32.num_digits(), 4); + assert_eq!(9_999_u32.num_digits(), 4); + assert_eq!(10_000_u32.num_digits(), 5); + assert_eq!(99_999_u32.num_digits(), 5); + assert_eq!(100_000_u32.num_digits(), 6); + assert_eq!(999_999_u32.num_digits(), 6); + assert_eq!(1_000_000_u32.num_digits(), 7); + assert_eq!(9_999_999_u32.num_digits(), 7); + assert_eq!(10_000_000_u32.num_digits(), 8); + assert_eq!(99_999_999_u32.num_digits(), 8); + assert_eq!(100_000_000_u32.num_digits(), 9); + assert_eq!(999_999_999_u32.num_digits(), 9); + assert_eq!(1_000_000_000_u32.num_digits(), 10); +} + +#[test] +fn default() { + assert_eq!( + duration::Padding::Optimize.clone(), + duration::Padding::default() + ); +} + +#[test] +fn debug() { + let _ = format!("{:?}", duration::Padding::Optimize); + let _ = format!("{:?}", parsing::ParsedItem(b"", 0)); + let _ = format!("{:?}", parsing::component::Period::Am); + let _ = format!("{:?}", iso8601::ExtendedKind::Basic); +} + +#[test] +fn clone() { + assert_eq!( + parsing::component::Period::Am.clone(), + parsing::component::Period::Am + ); + // does not impl Debug + assert!(crate::time::Padding::Optimize.clone() == crate::time::Padding::Optimize); + // does not impl PartialEq + assert!(matches!( + iso8601::ExtendedKind::Basic.clone(), + iso8601::ExtendedKind::Basic + )); +} + +#[test] +fn parsing_internals() { + assert!( + parsing::ParsedItem(b"", ()) + .flat_map(|_| None::<()>) + .is_none() + ); + assert!(<NonZeroU8 as Integer>::parse_bytes(b"256").is_none()); +} diff --git a/third_party/rust/time/src/time.rs b/third_party/rust/time/src/time.rs new file mode 100644 index 0000000000..32fa97790f --- /dev/null +++ b/third_party/rust/time/src/time.rs @@ -0,0 +1,761 @@ +//! The [`Time`] struct and its associated `impl`s. + +use core::fmt; +use core::ops::{Add, Sub}; +use core::time::Duration as StdDuration; +#[cfg(feature = "formatting")] +use std::io; + +#[cfg(feature = "formatting")] +use crate::formatting::Formattable; +#[cfg(feature = "parsing")] +use crate::parsing::Parsable; +use crate::util::DateAdjustment; +use crate::{error, Duration}; + +/// By explicitly inserting this enum where padding is expected, the compiler is able to better +/// perform niche value optimization. +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum Padding { + #[allow(clippy::missing_docs_in_private_items)] + Optimize, +} + +/// The clock time within a given date. Nanosecond precision. +/// +/// All minutes are assumed to have exactly 60 seconds; no attempt is made to handle leap seconds +/// (either positive or negative). +/// +/// When comparing two `Time`s, they are assumed to be in the same calendar date. +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Time { + #[allow(clippy::missing_docs_in_private_items)] + hour: u8, + #[allow(clippy::missing_docs_in_private_items)] + minute: u8, + #[allow(clippy::missing_docs_in_private_items)] + second: u8, + #[allow(clippy::missing_docs_in_private_items)] + nanosecond: u32, + #[allow(clippy::missing_docs_in_private_items)] + padding: Padding, +} + +impl Time { + /// Create a `Time` that is exactly midnight. + /// + /// ```rust + /// # use time::Time; + /// # use time_macros::time; + /// assert_eq!(Time::MIDNIGHT, time!(0:00)); + /// ``` + pub const MIDNIGHT: Self = Self::__from_hms_nanos_unchecked(0, 0, 0, 0); + + /// The smallest value that can be represented by `Time`. + /// + /// `00:00:00.0` + pub(crate) const MIN: Self = Self::__from_hms_nanos_unchecked(0, 0, 0, 0); + + /// The largest value that can be represented by `Time`. + /// + /// `23:59:59.999_999_999` + pub(crate) const MAX: Self = Self::__from_hms_nanos_unchecked(23, 59, 59, 999_999_999); + + // region: constructors + /// Create a `Time` from its components. + #[doc(hidden)] + pub const fn __from_hms_nanos_unchecked( + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + ) -> Self { + debug_assert!(hour < 24); + debug_assert!(minute < 60); + debug_assert!(second < 60); + debug_assert!(nanosecond < 1_000_000_000); + + Self { + hour, + minute, + second, + nanosecond, + padding: Padding::Optimize, + } + } + + /// Attempt to create a `Time` from the hour, minute, and second. + /// + /// ```rust + /// # use time::Time; + /// assert!(Time::from_hms(1, 2, 3).is_ok()); + /// ``` + /// + /// ```rust + /// # use time::Time; + /// assert!(Time::from_hms(24, 0, 0).is_err()); // 24 isn't a valid hour. + /// assert!(Time::from_hms(0, 60, 0).is_err()); // 60 isn't a valid minute. + /// assert!(Time::from_hms(0, 0, 60).is_err()); // 60 isn't a valid second. + /// ``` + pub const fn from_hms(hour: u8, minute: u8, second: u8) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(hour in 0 => 23); + ensure_value_in_range!(minute in 0 => 59); + ensure_value_in_range!(second in 0 => 59); + Ok(Self::__from_hms_nanos_unchecked(hour, minute, second, 0)) + } + + /// Attempt to create a `Time` from the hour, minute, second, and millisecond. + /// + /// ```rust + /// # use time::Time; + /// assert!(Time::from_hms_milli(1, 2, 3, 4).is_ok()); + /// ``` + /// + /// ```rust + /// # use time::Time; + /// assert!(Time::from_hms_milli(24, 0, 0, 0).is_err()); // 24 isn't a valid hour. + /// assert!(Time::from_hms_milli(0, 60, 0, 0).is_err()); // 60 isn't a valid minute. + /// assert!(Time::from_hms_milli(0, 0, 60, 0).is_err()); // 60 isn't a valid second. + /// assert!(Time::from_hms_milli(0, 0, 0, 1_000).is_err()); // 1_000 isn't a valid millisecond. + /// ``` + pub const fn from_hms_milli( + hour: u8, + minute: u8, + second: u8, + millisecond: u16, + ) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(hour in 0 => 23); + ensure_value_in_range!(minute in 0 => 59); + ensure_value_in_range!(second in 0 => 59); + ensure_value_in_range!(millisecond in 0 => 999); + Ok(Self::__from_hms_nanos_unchecked( + hour, + minute, + second, + millisecond as u32 * 1_000_000, + )) + } + + /// Attempt to create a `Time` from the hour, minute, second, and microsecond. + /// + /// ```rust + /// # use time::Time; + /// assert!(Time::from_hms_micro(1, 2, 3, 4).is_ok()); + /// ``` + /// + /// ```rust + /// # use time::Time; + /// assert!(Time::from_hms_micro(24, 0, 0, 0).is_err()); // 24 isn't a valid hour. + /// assert!(Time::from_hms_micro(0, 60, 0, 0).is_err()); // 60 isn't a valid minute. + /// assert!(Time::from_hms_micro(0, 0, 60, 0).is_err()); // 60 isn't a valid second. + /// assert!(Time::from_hms_micro(0, 0, 0, 1_000_000).is_err()); // 1_000_000 isn't a valid microsecond. + /// ``` + pub const fn from_hms_micro( + hour: u8, + minute: u8, + second: u8, + microsecond: u32, + ) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(hour in 0 => 23); + ensure_value_in_range!(minute in 0 => 59); + ensure_value_in_range!(second in 0 => 59); + ensure_value_in_range!(microsecond in 0 => 999_999); + Ok(Self::__from_hms_nanos_unchecked( + hour, + minute, + second, + microsecond * 1_000, + )) + } + + /// Attempt to create a `Time` from the hour, minute, second, and nanosecond. + /// + /// ```rust + /// # use time::Time; + /// assert!(Time::from_hms_nano(1, 2, 3, 4).is_ok()); + /// ``` + /// + /// ```rust + /// # use time::Time; + /// assert!(Time::from_hms_nano(24, 0, 0, 0).is_err()); // 24 isn't a valid hour. + /// assert!(Time::from_hms_nano(0, 60, 0, 0).is_err()); // 60 isn't a valid minute. + /// assert!(Time::from_hms_nano(0, 0, 60, 0).is_err()); // 60 isn't a valid second. + /// assert!(Time::from_hms_nano(0, 0, 0, 1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond. + /// ``` + pub const fn from_hms_nano( + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + ) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(hour in 0 => 23); + ensure_value_in_range!(minute in 0 => 59); + ensure_value_in_range!(second in 0 => 59); + ensure_value_in_range!(nanosecond in 0 => 999_999_999); + Ok(Self::__from_hms_nanos_unchecked( + hour, minute, second, nanosecond, + )) + } + // endregion constructors + + // region: getters + /// Get the clock hour, minute, and second. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00:00).as_hms(), (0, 0, 0)); + /// assert_eq!(time!(23:59:59).as_hms(), (23, 59, 59)); + /// ``` + pub const fn as_hms(self) -> (u8, u8, u8) { + (self.hour, self.minute, self.second) + } + + /// Get the clock hour, minute, second, and millisecond. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00:00).as_hms_milli(), (0, 0, 0, 0)); + /// assert_eq!(time!(23:59:59.999).as_hms_milli(), (23, 59, 59, 999)); + /// ``` + pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) { + ( + self.hour, + self.minute, + self.second, + (self.nanosecond / 1_000_000) as u16, + ) + } + + /// Get the clock hour, minute, second, and microsecond. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00:00).as_hms_micro(), (0, 0, 0, 0)); + /// assert_eq!( + /// time!(23:59:59.999_999).as_hms_micro(), + /// (23, 59, 59, 999_999) + /// ); + /// ``` + pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) { + (self.hour, self.minute, self.second, self.nanosecond / 1_000) + } + + /// Get the clock hour, minute, second, and nanosecond. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00:00).as_hms_nano(), (0, 0, 0, 0)); + /// assert_eq!( + /// time!(23:59:59.999_999_999).as_hms_nano(), + /// (23, 59, 59, 999_999_999) + /// ); + /// ``` + pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) { + (self.hour, self.minute, self.second, self.nanosecond) + } + + /// Get the clock hour. + /// + /// The returned value will always be in the range `0..24`. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00:00).hour(), 0); + /// assert_eq!(time!(23:59:59).hour(), 23); + /// ``` + pub const fn hour(self) -> u8 { + self.hour + } + + /// Get the minute within the hour. + /// + /// The returned value will always be in the range `0..60`. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00:00).minute(), 0); + /// assert_eq!(time!(23:59:59).minute(), 59); + /// ``` + pub const fn minute(self) -> u8 { + self.minute + } + + /// Get the second within the minute. + /// + /// The returned value will always be in the range `0..60`. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00:00).second(), 0); + /// assert_eq!(time!(23:59:59).second(), 59); + /// ``` + pub const fn second(self) -> u8 { + self.second + } + + /// Get the milliseconds within the second. + /// + /// The returned value will always be in the range `0..1_000`. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00).millisecond(), 0); + /// assert_eq!(time!(23:59:59.999).millisecond(), 999); + /// ``` + pub const fn millisecond(self) -> u16 { + (self.nanosecond / 1_000_000) as _ + } + + /// Get the microseconds within the second. + /// + /// The returned value will always be in the range `0..1_000_000`. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00).microsecond(), 0); + /// assert_eq!(time!(23:59:59.999_999).microsecond(), 999_999); + /// ``` + pub const fn microsecond(self) -> u32 { + self.nanosecond / 1_000 + } + + /// Get the nanoseconds within the second. + /// + /// The returned value will always be in the range `0..1_000_000_000`. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!(time!(0:00).nanosecond(), 0); + /// assert_eq!(time!(23:59:59.999_999_999).nanosecond(), 999_999_999); + /// ``` + pub const fn nanosecond(self) -> u32 { + self.nanosecond + } + // endregion getters + + // region: arithmetic helpers + /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning whether + /// the date is different. + pub(crate) const fn adjusting_add(self, duration: Duration) -> (DateAdjustment, Self) { + let mut nanoseconds = self.nanosecond as i32 + duration.subsec_nanoseconds(); + let mut seconds = self.second as i8 + (duration.whole_seconds() % 60) as i8; + let mut minutes = self.minute as i8 + (duration.whole_minutes() % 60) as i8; + let mut hours = self.hour as i8 + (duration.whole_hours() % 24) as i8; + let mut date_adjustment = DateAdjustment::None; + + cascade!(nanoseconds in 0..1_000_000_000 => seconds); + cascade!(seconds in 0..60 => minutes); + cascade!(minutes in 0..60 => hours); + if hours >= 24 { + hours -= 24; + date_adjustment = DateAdjustment::Next; + } else if hours < 0 { + hours += 24; + date_adjustment = DateAdjustment::Previous; + } + + ( + date_adjustment, + Self::__from_hms_nanos_unchecked( + hours as _, + minutes as _, + seconds as _, + nanoseconds as _, + ), + ) + } + + /// Subtract the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning + /// whether the date is different. + pub(crate) const fn adjusting_sub(self, duration: Duration) -> (DateAdjustment, Self) { + let mut nanoseconds = self.nanosecond as i32 - duration.subsec_nanoseconds(); + let mut seconds = self.second as i8 - (duration.whole_seconds() % 60) as i8; + let mut minutes = self.minute as i8 - (duration.whole_minutes() % 60) as i8; + let mut hours = self.hour as i8 - (duration.whole_hours() % 24) as i8; + let mut date_adjustment = DateAdjustment::None; + + cascade!(nanoseconds in 0..1_000_000_000 => seconds); + cascade!(seconds in 0..60 => minutes); + cascade!(minutes in 0..60 => hours); + if hours >= 24 { + hours -= 24; + date_adjustment = DateAdjustment::Next; + } else if hours < 0 { + hours += 24; + date_adjustment = DateAdjustment::Previous; + } + + ( + date_adjustment, + Self::__from_hms_nanos_unchecked( + hours as _, + minutes as _, + seconds as _, + nanoseconds as _, + ), + ) + } + + /// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow, + /// returning whether the date is the previous date as the first element of the tuple. + pub(crate) const fn adjusting_add_std(self, duration: StdDuration) -> (bool, Self) { + let mut nanosecond = self.nanosecond + duration.subsec_nanos(); + let mut second = self.second + (duration.as_secs() % 60) as u8; + let mut minute = self.minute + ((duration.as_secs() / 60) % 60) as u8; + let mut hour = self.hour + ((duration.as_secs() / 3_600) % 24) as u8; + let mut is_next_day = false; + + cascade!(nanosecond in 0..1_000_000_000 => second); + cascade!(second in 0..60 => minute); + cascade!(minute in 0..60 => hour); + if hour >= 24 { + hour -= 24; + is_next_day = true; + } + + ( + is_next_day, + Self::__from_hms_nanos_unchecked(hour, minute, second, nanosecond), + ) + } + + /// Subtract the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow, + /// returning whether the date is the previous date as the first element of the tuple. + pub(crate) const fn adjusting_sub_std(self, duration: StdDuration) -> (bool, Self) { + let mut nanosecond = self.nanosecond as i32 - duration.subsec_nanos() as i32; + let mut second = self.second as i8 - (duration.as_secs() % 60) as i8; + let mut minute = self.minute as i8 - ((duration.as_secs() / 60) % 60) as i8; + let mut hour = self.hour as i8 - ((duration.as_secs() / 3_600) % 24) as i8; + let mut is_previous_day = false; + + cascade!(nanosecond in 0..1_000_000_000 => second); + cascade!(second in 0..60 => minute); + cascade!(minute in 0..60 => hour); + if hour < 0 { + hour += 24; + is_previous_day = true; + } + + ( + is_previous_day, + Self::__from_hms_nanos_unchecked(hour as _, minute as _, second as _, nanosecond as _), + ) + } + // endregion arithmetic helpers + + // region: replacement + /// Replace the clock hour. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!( + /// time!(01:02:03.004_005_006).replace_hour(7), + /// Ok(time!(07:02:03.004_005_006)) + /// ); + /// assert!(time!(01:02:03.004_005_006).replace_hour(24).is_err()); // 24 isn't a valid hour + /// ``` + #[must_use = "This method does not mutate the original `Time`."] + pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(hour in 0 => 23); + Ok(Self::__from_hms_nanos_unchecked( + hour, + self.minute, + self.second, + self.nanosecond, + )) + } + + /// Replace the minutes within the hour. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!( + /// time!(01:02:03.004_005_006).replace_minute(7), + /// Ok(time!(01:07:03.004_005_006)) + /// ); + /// assert!(time!(01:02:03.004_005_006).replace_minute(60).is_err()); // 60 isn't a valid minute + /// ``` + #[must_use = "This method does not mutate the original `Time`."] + pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(minute in 0 => 59); + Ok(Self::__from_hms_nanos_unchecked( + self.hour, + minute, + self.second, + self.nanosecond, + )) + } + + /// Replace the seconds within the minute. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!( + /// time!(01:02:03.004_005_006).replace_second(7), + /// Ok(time!(01:02:07.004_005_006)) + /// ); + /// assert!(time!(01:02:03.004_005_006).replace_second(60).is_err()); // 60 isn't a valid second + /// ``` + #[must_use = "This method does not mutate the original `Time`."] + pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(second in 0 => 59); + Ok(Self::__from_hms_nanos_unchecked( + self.hour, + self.minute, + second, + self.nanosecond, + )) + } + + /// Replace the milliseconds within the second. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!( + /// time!(01:02:03.004_005_006).replace_millisecond(7), + /// Ok(time!(01:02:03.007)) + /// ); + /// assert!(time!(01:02:03.004_005_006).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond + /// ``` + #[must_use = "This method does not mutate the original `Time`."] + pub const fn replace_millisecond( + self, + millisecond: u16, + ) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(millisecond in 0 => 999); + Ok(Self::__from_hms_nanos_unchecked( + self.hour, + self.minute, + self.second, + millisecond as u32 * 1_000_000, + )) + } + + /// Replace the microseconds within the second. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!( + /// time!(01:02:03.004_005_006).replace_microsecond(7_008), + /// Ok(time!(01:02:03.007_008)) + /// ); + /// assert!(time!(01:02:03.004_005_006).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond + /// ``` + #[must_use = "This method does not mutate the original `Time`."] + pub const fn replace_microsecond( + self, + microsecond: u32, + ) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(microsecond in 0 => 999_999); + Ok(Self::__from_hms_nanos_unchecked( + self.hour, + self.minute, + self.second, + microsecond * 1000, + )) + } + + /// Replace the nanoseconds within the second. + /// + /// ```rust + /// # use time_macros::time; + /// assert_eq!( + /// time!(01:02:03.004_005_006).replace_nanosecond(7_008_009), + /// Ok(time!(01:02:03.007_008_009)) + /// ); + /// assert!(time!(01:02:03.004_005_006).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond + /// ``` + #[must_use = "This method does not mutate the original `Time`."] + pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(nanosecond in 0 => 999_999_999); + Ok(Self::__from_hms_nanos_unchecked( + self.hour, + self.minute, + self.second, + nanosecond, + )) + } + // endregion replacement +} + +// region: formatting & parsing +#[cfg(feature = "formatting")] +impl Time { + /// Format the `Time` using the provided [format description](crate::format_description). + pub fn format_into( + self, + output: &mut impl io::Write, + format: &(impl Formattable + ?Sized), + ) -> Result<usize, crate::error::Format> { + format.format_into(output, None, Some(self), None) + } + + /// Format the `Time` using the provided [format description](crate::format_description). + /// + /// ```rust + /// # use time::format_description; + /// # use time_macros::time; + /// let format = format_description::parse("[hour]:[minute]:[second]")?; + /// assert_eq!(time!(12:00).format(&format)?, "12:00:00"); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn format( + self, + format: &(impl Formattable + ?Sized), + ) -> Result<String, crate::error::Format> { + format.format(None, Some(self), None) + } +} + +#[cfg(feature = "parsing")] +impl Time { + /// Parse a `Time` from the input using the provided [format + /// description](crate::format_description). + /// + /// ```rust + /// # use time::Time; + /// # use time_macros::{time, format_description}; + /// let format = format_description!("[hour]:[minute]:[second]"); + /// assert_eq!(Time::parse("12:00:00", &format)?, time!(12:00)); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn parse( + input: &str, + description: &(impl Parsable + ?Sized), + ) -> Result<Self, error::Parse> { + description.parse_time(input.as_bytes()) + } +} + +impl fmt::Display for Time { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (value, width) = match self.nanosecond() { + nanos if nanos % 10 != 0 => (nanos, 9), + nanos if (nanos / 10) % 10 != 0 => (nanos / 10, 8), + nanos if (nanos / 100) % 10 != 0 => (nanos / 100, 7), + nanos if (nanos / 1_000) % 10 != 0 => (nanos / 1_000, 6), + nanos if (nanos / 10_000) % 10 != 0 => (nanos / 10_000, 5), + nanos if (nanos / 100_000) % 10 != 0 => (nanos / 100_000, 4), + nanos if (nanos / 1_000_000) % 10 != 0 => (nanos / 1_000_000, 3), + nanos if (nanos / 10_000_000) % 10 != 0 => (nanos / 10_000_000, 2), + nanos => (nanos / 100_000_000, 1), + }; + write!( + f, + "{}:{:02}:{:02}.{value:0width$}", + self.hour, self.minute, self.second, + ) + } +} + +impl fmt::Debug for Time { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} +// endregion formatting & parsing + +// region: trait impls +impl Add<Duration> for Time { + type Output = Self; + + /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// # use time_macros::time; + /// assert_eq!(time!(12:00) + 2.hours(), time!(14:00)); + /// assert_eq!(time!(0:00:01) + (-2).seconds(), time!(23:59:59)); + /// ``` + fn add(self, duration: Duration) -> Self::Output { + self.adjusting_add(duration).1 + } +} + +impl Add<StdDuration> for Time { + type Output = Self; + + /// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow. + /// + /// ```rust + /// # use time::ext::NumericalStdDuration; + /// # use time_macros::time; + /// assert_eq!(time!(12:00) + 2.std_hours(), time!(14:00)); + /// assert_eq!(time!(23:59:59) + 2.std_seconds(), time!(0:00:01)); + /// ``` + fn add(self, duration: StdDuration) -> Self::Output { + self.adjusting_add_std(duration).1 + } +} + +impl_add_assign!(Time: Duration, StdDuration); + +impl Sub<Duration> for Time { + type Output = Self; + + /// Subtract the sub-day time of the [`Duration`] from the `Time`. Wraps on overflow. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// # use time_macros::time; + /// assert_eq!(time!(14:00) - 2.hours(), time!(12:00)); + /// assert_eq!(time!(23:59:59) - (-2).seconds(), time!(0:00:01)); + /// ``` + fn sub(self, duration: Duration) -> Self::Output { + self.adjusting_sub(duration).1 + } +} + +impl Sub<StdDuration> for Time { + type Output = Self; + + /// Subtract the sub-day time of the [`std::time::Duration`] from the `Time`. Wraps on overflow. + /// + /// ```rust + /// # use time::ext::NumericalStdDuration; + /// # use time_macros::time; + /// assert_eq!(time!(14:00) - 2.std_hours(), time!(12:00)); + /// assert_eq!(time!(0:00:01) - 2.std_seconds(), time!(23:59:59)); + /// ``` + fn sub(self, duration: StdDuration) -> Self::Output { + self.adjusting_sub_std(duration).1 + } +} + +impl_sub_assign!(Time: Duration, StdDuration); + +impl Sub for Time { + type Output = Duration; + + /// Subtract two `Time`s, returning the [`Duration`] between. This assumes both `Time`s are in + /// the same calendar day. + /// + /// ```rust + /// # use time::ext::NumericalDuration; + /// # use time_macros::time; + /// assert_eq!(time!(0:00) - time!(0:00), 0.seconds()); + /// assert_eq!(time!(1:00) - time!(0:00), 1.hours()); + /// assert_eq!(time!(0:00) - time!(1:00), (-1).hours()); + /// assert_eq!(time!(0:00) - time!(23:00), (-23).hours()); + /// ``` + fn sub(self, rhs: Self) -> Self::Output { + let hour_diff = (self.hour as i8) - (rhs.hour as i8); + let minute_diff = (self.minute as i8) - (rhs.minute as i8); + let second_diff = (self.second as i8) - (rhs.second as i8); + let nanosecond_diff = (self.nanosecond as i32) - (rhs.nanosecond as i32); + + let seconds = hour_diff as i64 * 3_600 + minute_diff as i64 * 60 + second_diff as i64; + + let (seconds, nanoseconds) = if seconds > 0 && nanosecond_diff < 0 { + (seconds - 1, nanosecond_diff + 1_000_000_000) + } else if seconds < 0 && nanosecond_diff > 0 { + (seconds + 1, nanosecond_diff - 1_000_000_000) + } else { + (seconds, nanosecond_diff) + }; + + Duration::new_unchecked(seconds, nanoseconds) + } +} +// endregion trait impls diff --git a/third_party/rust/time/src/utc_offset.rs b/third_party/rust/time/src/utc_offset.rs new file mode 100644 index 0000000000..d69f0c1e39 --- /dev/null +++ b/third_party/rust/time/src/utc_offset.rs @@ -0,0 +1,346 @@ +//! The [`UtcOffset`] struct and its associated `impl`s. + +use core::fmt; +use core::ops::Neg; +#[cfg(feature = "formatting")] +use std::io; + +use crate::error; +#[cfg(feature = "formatting")] +use crate::formatting::Formattable; +#[cfg(feature = "parsing")] +use crate::parsing::Parsable; +#[cfg(feature = "local-offset")] +use crate::sys::local_offset_at; +#[cfg(feature = "local-offset")] +use crate::OffsetDateTime; + +/// An offset from UTC. +/// +/// This struct can store values up to ±23:59:59. If you need support outside this range, please +/// file an issue with your use case. +// All three components _must_ have the same sign. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UtcOffset { + #[allow(clippy::missing_docs_in_private_items)] + hours: i8, + #[allow(clippy::missing_docs_in_private_items)] + minutes: i8, + #[allow(clippy::missing_docs_in_private_items)] + seconds: i8, +} + +impl UtcOffset { + /// A `UtcOffset` that is UTC. + /// + /// ```rust + /// # use time::UtcOffset; + /// # use time_macros::offset; + /// assert_eq!(UtcOffset::UTC, offset!(UTC)); + /// ``` + pub const UTC: Self = Self::__from_hms_unchecked(0, 0, 0); + + // region: constructors + /// Create a `UtcOffset` representing an offset of the hours, minutes, and seconds provided, the + /// validity of which must be guaranteed by the caller. All three parameters must have the same + /// sign. + #[doc(hidden)] + pub const fn __from_hms_unchecked(hours: i8, minutes: i8, seconds: i8) -> Self { + if hours < 0 { + debug_assert!(minutes <= 0); + debug_assert!(seconds <= 0); + } else if hours > 0 { + debug_assert!(minutes >= 0); + debug_assert!(seconds >= 0); + } + if minutes < 0 { + debug_assert!(seconds <= 0); + } else if minutes > 0 { + debug_assert!(seconds >= 0); + } + debug_assert!(hours.unsigned_abs() < 24); + debug_assert!(minutes.unsigned_abs() < 60); + debug_assert!(seconds.unsigned_abs() < 60); + + Self { + hours, + minutes, + seconds, + } + } + + /// Create a `UtcOffset` representing an offset by the number of hours, minutes, and seconds + /// provided. + /// + /// The sign of all three components should match. If they do not, all smaller components will + /// have their signs flipped. + /// + /// ```rust + /// # use time::UtcOffset; + /// assert_eq!(UtcOffset::from_hms(1, 2, 3)?.as_hms(), (1, 2, 3)); + /// assert_eq!(UtcOffset::from_hms(1, -2, -3)?.as_hms(), (1, 2, 3)); + /// # Ok::<_, time::Error>(()) + /// ``` + pub const fn from_hms( + hours: i8, + mut minutes: i8, + mut seconds: i8, + ) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(hours in -23 => 23); + ensure_value_in_range!(minutes in -59 => 59); + ensure_value_in_range!(seconds in -59 => 59); + + if (hours > 0 && minutes < 0) || (hours < 0 && minutes > 0) { + minutes *= -1; + } + if (hours > 0 && seconds < 0) + || (hours < 0 && seconds > 0) + || (minutes > 0 && seconds < 0) + || (minutes < 0 && seconds > 0) + { + seconds *= -1; + } + + Ok(Self::__from_hms_unchecked(hours, minutes, seconds)) + } + + /// Create a `UtcOffset` representing an offset by the number of seconds provided. + /// + /// ```rust + /// # use time::UtcOffset; + /// assert_eq!(UtcOffset::from_whole_seconds(3_723)?.as_hms(), (1, 2, 3)); + /// # Ok::<_, time::Error>(()) + /// ``` + pub const fn from_whole_seconds(seconds: i32) -> Result<Self, error::ComponentRange> { + ensure_value_in_range!(seconds in -86_399 => 86_399); + + Ok(Self::__from_hms_unchecked( + (seconds / 3_600) as _, + ((seconds / 60) % 60) as _, + (seconds % 60) as _, + )) + } + // endregion constructors + + // region: getters + /// Obtain the UTC offset as its hours, minutes, and seconds. The sign of all three components + /// will always match. A positive value indicates an offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).as_hms(), (1, 2, 3)); + /// assert_eq!(offset!(-1:02:03).as_hms(), (-1, -2, -3)); + /// ``` + pub const fn as_hms(self) -> (i8, i8, i8) { + (self.hours, self.minutes, self.seconds) + } + + /// Obtain the number of whole hours the offset is from UTC. A positive value indicates an + /// offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).whole_hours(), 1); + /// assert_eq!(offset!(-1:02:03).whole_hours(), -1); + /// ``` + pub const fn whole_hours(self) -> i8 { + self.hours + } + + /// Obtain the number of whole minutes the offset is from UTC. A positive value indicates an + /// offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).whole_minutes(), 62); + /// assert_eq!(offset!(-1:02:03).whole_minutes(), -62); + /// ``` + pub const fn whole_minutes(self) -> i16 { + self.hours as i16 * 60 + self.minutes as i16 + } + + /// Obtain the number of minutes past the hour the offset is from UTC. A positive value + /// indicates an offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).minutes_past_hour(), 2); + /// assert_eq!(offset!(-1:02:03).minutes_past_hour(), -2); + /// ``` + pub const fn minutes_past_hour(self) -> i8 { + self.minutes + } + + /// Obtain the number of whole seconds the offset is from UTC. A positive value indicates an + /// offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).whole_seconds(), 3723); + /// assert_eq!(offset!(-1:02:03).whole_seconds(), -3723); + /// ``` + // This may be useful for anyone manually implementing arithmetic, as it + // would let them construct a `Duration` directly. + pub const fn whole_seconds(self) -> i32 { + self.hours as i32 * 3_600 + self.minutes as i32 * 60 + self.seconds as i32 + } + + /// Obtain the number of seconds past the minute the offset is from UTC. A positive value + /// indicates an offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).seconds_past_minute(), 3); + /// assert_eq!(offset!(-1:02:03).seconds_past_minute(), -3); + /// ``` + pub const fn seconds_past_minute(self) -> i8 { + self.seconds + } + // endregion getters + + // region: is_{sign} + /// Check if the offset is exactly UTC. + /// + /// + /// ```rust + /// # use time_macros::offset; + /// assert!(!offset!(+1:02:03).is_utc()); + /// assert!(!offset!(-1:02:03).is_utc()); + /// assert!(offset!(UTC).is_utc()); + /// ``` + pub const fn is_utc(self) -> bool { + self.hours == 0 && self.minutes == 0 && self.seconds == 0 + } + + /// Check if the offset is positive, or east of UTC. + /// + /// ```rust + /// # use time_macros::offset; + /// assert!(offset!(+1:02:03).is_positive()); + /// assert!(!offset!(-1:02:03).is_positive()); + /// assert!(!offset!(UTC).is_positive()); + /// ``` + pub const fn is_positive(self) -> bool { + self.hours > 0 || self.minutes > 0 || self.seconds > 0 + } + + /// Check if the offset is negative, or west of UTC. + /// + /// ```rust + /// # use time_macros::offset; + /// assert!(!offset!(+1:02:03).is_negative()); + /// assert!(offset!(-1:02:03).is_negative()); + /// assert!(!offset!(UTC).is_negative()); + /// ``` + pub const fn is_negative(self) -> bool { + self.hours < 0 || self.minutes < 0 || self.seconds < 0 + } + // endregion is_{sign} + + // region: local offset + /// Attempt to obtain the system's UTC offset at a known moment in time. If the offset cannot be + /// determined, an error is returned. + /// + /// ```rust + /// # use time::{UtcOffset, OffsetDateTime}; + /// let local_offset = UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH); + /// # if false { + /// assert!(local_offset.is_ok()); + /// # } + /// ``` + #[cfg(feature = "local-offset")] + pub fn local_offset_at(datetime: OffsetDateTime) -> Result<Self, error::IndeterminateOffset> { + local_offset_at(datetime).ok_or(error::IndeterminateOffset) + } + + /// Attempt to obtain the system's current UTC offset. If the offset cannot be determined, an + /// error is returned. + /// + /// ```rust + /// # use time::UtcOffset; + /// let local_offset = UtcOffset::current_local_offset(); + /// # if false { + /// assert!(local_offset.is_ok()); + /// # } + /// ``` + #[cfg(feature = "local-offset")] + pub fn current_local_offset() -> Result<Self, error::IndeterminateOffset> { + let now = OffsetDateTime::now_utc(); + local_offset_at(now).ok_or(error::IndeterminateOffset) + } + // endregion: local offset +} + +// region: formatting & parsing +#[cfg(feature = "formatting")] +impl UtcOffset { + /// Format the `UtcOffset` using the provided [format description](crate::format_description). + pub fn format_into( + self, + output: &mut impl io::Write, + format: &(impl Formattable + ?Sized), + ) -> Result<usize, error::Format> { + format.format_into(output, None, None, Some(self)) + } + + /// Format the `UtcOffset` using the provided [format description](crate::format_description). + /// + /// ```rust + /// # use time::format_description; + /// # use time_macros::offset; + /// let format = format_description::parse("[offset_hour sign:mandatory]:[offset_minute]")?; + /// assert_eq!(offset!(+1).format(&format)?, "+01:00"); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { + format.format(None, None, Some(self)) + } +} + +#[cfg(feature = "parsing")] +impl UtcOffset { + /// Parse a `UtcOffset` from the input using the provided [format + /// description](crate::format_description). + /// + /// ```rust + /// # use time::UtcOffset; + /// # use time_macros::{offset, format_description}; + /// let format = format_description!("[offset_hour]:[offset_minute]"); + /// assert_eq!(UtcOffset::parse("-03:42", &format)?, offset!(-3:42)); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn parse( + input: &str, + description: &(impl Parsable + ?Sized), + ) -> Result<Self, error::Parse> { + description.parse_offset(input.as_bytes()) + } +} + +impl fmt::Display for UtcOffset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}{:02}:{:02}:{:02}", + if self.is_negative() { '-' } else { '+' }, + self.hours.abs(), + self.minutes.abs(), + self.seconds.abs() + ) + } +} + +impl fmt::Debug for UtcOffset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} +// endregion formatting & parsing + +impl Neg for UtcOffset { + type Output = Self; + + fn neg(self) -> Self::Output { + Self::__from_hms_unchecked(-self.hours, -self.minutes, -self.seconds) + } +} diff --git a/third_party/rust/time/src/util.rs b/third_party/rust/time/src/util.rs new file mode 100644 index 0000000000..11330501ca --- /dev/null +++ b/third_party/rust/time/src/util.rs @@ -0,0 +1,31 @@ +//! Utility functions. + +pub use time_core::util::{days_in_year, is_leap_year, weeks_in_year}; + +use crate::Month; + +/// Whether to adjust the date, and in which direction. Useful when implementing arithmetic. +pub(crate) enum DateAdjustment { + /// The previous day should be used. + Previous, + /// The next day should be used. + Next, + /// The date should be used as-is. + None, +} + +/// Get the number of days in the month of a given year. +/// +/// ```rust +/// # use time::{Month, util}; +/// assert_eq!(util::days_in_year_month(2020, Month::February), 29); +/// ``` +pub const fn days_in_year_month(year: i32, month: Month) -> u8 { + use Month::*; + match month { + January | March | May | July | August | October | December => 31, + April | June | September | November => 30, + February if is_leap_year(year) => 29, + February => 28, + } +} diff --git a/third_party/rust/time/src/weekday.rs b/third_party/rust/time/src/weekday.rs new file mode 100644 index 0000000000..f499c30eef --- /dev/null +++ b/third_party/rust/time/src/weekday.rs @@ -0,0 +1,148 @@ +//! Days of the week. + +use core::fmt::{self, Display}; +use core::str::FromStr; + +use Weekday::*; + +use crate::error; + +/// Days of the week. +/// +/// As order is dependent on context (Sunday could be either two days after or five days before +/// Friday), this type does not implement `PartialOrd` or `Ord`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Weekday { + #[allow(clippy::missing_docs_in_private_items)] + Monday, + #[allow(clippy::missing_docs_in_private_items)] + Tuesday, + #[allow(clippy::missing_docs_in_private_items)] + Wednesday, + #[allow(clippy::missing_docs_in_private_items)] + Thursday, + #[allow(clippy::missing_docs_in_private_items)] + Friday, + #[allow(clippy::missing_docs_in_private_items)] + Saturday, + #[allow(clippy::missing_docs_in_private_items)] + Sunday, +} + +impl Weekday { + /// Get the previous weekday. + /// + /// ```rust + /// # use time::Weekday; + /// assert_eq!(Weekday::Tuesday.previous(), Weekday::Monday); + /// ``` + pub const fn previous(self) -> Self { + match self { + Monday => Sunday, + Tuesday => Monday, + Wednesday => Tuesday, + Thursday => Wednesday, + Friday => Thursday, + Saturday => Friday, + Sunday => Saturday, + } + } + + /// Get the next weekday. + /// + /// ```rust + /// # use time::Weekday; + /// assert_eq!(Weekday::Monday.next(), Weekday::Tuesday); + /// ``` + pub const fn next(self) -> Self { + match self { + Monday => Tuesday, + Tuesday => Wednesday, + Wednesday => Thursday, + Thursday => Friday, + Friday => Saturday, + Saturday => Sunday, + Sunday => Monday, + } + } + + /// Get the one-indexed number of days from Monday. + /// + /// ```rust + /// # use time::Weekday; + /// assert_eq!(Weekday::Monday.number_from_monday(), 1); + /// ``` + #[doc(alias = "iso_weekday_number")] + pub const fn number_from_monday(self) -> u8 { + self.number_days_from_monday() + 1 + } + + /// Get the one-indexed number of days from Sunday. + /// + /// ```rust + /// # use time::Weekday; + /// assert_eq!(Weekday::Monday.number_from_sunday(), 2); + /// ``` + pub const fn number_from_sunday(self) -> u8 { + self.number_days_from_sunday() + 1 + } + + /// Get the zero-indexed number of days from Monday. + /// + /// ```rust + /// # use time::Weekday; + /// assert_eq!(Weekday::Monday.number_days_from_monday(), 0); + /// ``` + pub const fn number_days_from_monday(self) -> u8 { + self as _ + } + + /// Get the zero-indexed number of days from Sunday. + /// + /// ```rust + /// # use time::Weekday; + /// assert_eq!(Weekday::Monday.number_days_from_sunday(), 1); + /// ``` + pub const fn number_days_from_sunday(self) -> u8 { + match self { + Monday => 1, + Tuesday => 2, + Wednesday => 3, + Thursday => 4, + Friday => 5, + Saturday => 6, + Sunday => 0, + } + } +} + +impl Display for Weekday { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Monday => "Monday", + Tuesday => "Tuesday", + Wednesday => "Wednesday", + Thursday => "Thursday", + Friday => "Friday", + Saturday => "Saturday", + Sunday => "Sunday", + }) + } +} + +impl FromStr for Weekday { + type Err = error::InvalidVariant; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "Monday" => Ok(Monday), + "Tuesday" => Ok(Tuesday), + "Wednesday" => Ok(Wednesday), + "Thursday" => Ok(Thursday), + "Friday" => Ok(Friday), + "Saturday" => Ok(Saturday), + "Sunday" => Ok(Sunday), + _ => Err(error::InvalidVariant), + } + } +} |