time-0.3.37/.cargo_vcs_info.json0000644000000001420000000000100120610ustar { "git": { "sha1": "d4e39b306db1624795cd4eb0cfde8cc802edd0e3" }, "path_in_vcs": "time" }time-0.3.37/Cargo.toml0000644000000141100000000000100100570ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.67.1" name = "time" version = "0.3.37" authors = [ "Jacob Pratt ", "Time contributors", ] build = false include = [ "src/**/*", "LICENSE-*", "README.md", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Date and time library. Fully interoperable with the standard library. Mostly compatible with #![no_std]." homepage = "https://time-rs.github.io" readme = "README.md" keywords = [ "date", "time", "calendar", "duration", ] categories = [ "date-and-time", "no-std", "parser-implementations", "value-formatting", ] license = "MIT OR Apache-2.0" repository = "https://github.com/time-rs/time" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--generate-link-to-definition"] targets = ["x86_64-unknown-linux-gnu"] [lib] name = "time" path = "src/lib.rs" bench = false [dependencies.deranged] version = "0.3.9" features = ["powerfmt"] default-features = false [dependencies.itoa] version = "1.0.1" optional = true [dependencies.num-conv] version = "0.1.0" [dependencies.powerfmt] version = "0.2.0" default-features = false [dependencies.quickcheck] version = "1.0.3" optional = true default-features = false [dependencies.rand] version = "0.8.4" optional = true default-features = false [dependencies.serde] version = "1.0.184" optional = true default-features = false [dependencies.time-core] version = "=0.1.2" [dependencies.time-macros] version = "=0.2.19" optional = true [dev-dependencies.num-conv] version = "0.1.0" [dev-dependencies.quickcheck_macros] version = "1.0.0" [dev-dependencies.rand] version = "0.8.4" default-features = false [dev-dependencies.rstest] version = "0.23.0" default-features = false [dev-dependencies.rstest_reuse] version = "0.7.0" [dev-dependencies.serde] version = "1.0.184" 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.19" [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", "deranged/quickcheck", ] rand = [ "dep:rand", "deranged/rand", ] serde = [ "dep:serde", "time-macros?/serde", "deranged/serde", ] serde-human-readable = [ "serde", "formatting", "parsing", ] serde-well-known = [ "serde", "formatting", "parsing", ] std = [ "alloc", "deranged/std", ] wasm-bindgen = ["dep:js-sys"] [target."cfg(__ui_tests)".dev-dependencies.trybuild] version = "1.0.68" [target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies.js-sys] version = "0.3.58" optional = true [target."cfg(bench)".dev-dependencies.criterion] version = "0.5.1" default-features = false [target.'cfg(target_family = "unix")'.dependencies.libc] version = "0.2.98" optional = true [target.'cfg(target_family = "unix")'.dependencies.num_threads] version = "0.1.2" optional = true [lints.clippy] alloc-instead-of-core = "deny" dbg-macro = "warn" decimal-literal-representation = "warn" explicit-auto-deref = "warn" get-unwrap = "warn" manual-let-else = "warn" missing-docs-in-private-items = "warn" missing-enforced-import-renames = "warn" obfuscated-if-else = "warn" print-stdout = "warn" semicolon-outside-block = "warn" std-instead-of-core = "deny" todo = "warn" undocumented-unsafe-blocks = "deny" unimplemented = "warn" uninlined-format-args = "warn" unnested-or-patterns = "warn" unwrap-in-result = "warn" unwrap-used = "warn" use-debug = "warn" [lints.clippy.all] level = "warn" priority = -1 [lints.clippy.nursery] level = "warn" priority = -1 [lints.clippy.option-if-let-else] level = "allow" priority = 1 [lints.clippy.redundant-pub-crate] level = "allow" priority = 1 [lints.rust] ambiguous-glob-reexports = "deny" clashing-extern-declarations = "deny" const-item-mutation = "deny" dangling-pointers-from-temporaries = "deny" deref-nullptr = "deny" drop-bounds = "deny" future-incompatible = "deny" hidden-glob-reexports = "deny" improper-ctypes = "deny" improper-ctypes-definitions = "deny" invalid-from-utf8 = "deny" invalid-macro-export-arguments = "deny" invalid-nan-comparisons = "deny" invalid-reference-casting = "deny" invalid-value = "deny" keyword-idents = "warn" let-underscore = "warn" macro-use-extern-crate = "warn" meta-variable-misuse = "warn" missing-abi = "warn" missing-copy-implementations = "warn" missing-debug-implementations = "warn" missing-docs = "warn" named-arguments-used-positionally = "deny" non-ascii-idents = "deny" noop-method-call = "warn" opaque-hidden-inferred-bound = "deny" overlapping-range-endpoints = "deny" single-use-lifetimes = "warn" suspicious-double-ref-op = "deny" trivial-casts = "warn" trivial-numeric-casts = "warn" unconditional-recursion = "deny" unnameable-test-items = "deny" unreachable-pub = "warn" unsafe-op-in-unsafe-fn = "deny" unstable-syntax-pre-expansion = "deny" unused-import-braces = "warn" unused-lifetimes = "warn" unused-qualifications = "warn" variant-size-differences = "warn" [lints.rust.unexpected_cfgs] level = "deny" priority = 0 check-cfg = [ "cfg(__ui_tests)", "cfg(bench)", ] [lints.rust.unstable-name-collisions] level = "allow" priority = 1 [lints.rust.unused] level = "warn" priority = -1 [lints.rustdoc] private-doc-tests = "warn" unescaped-backticks = "warn" time-0.3.37/Cargo.toml.orig000064400000000000000000000056461046102023000135560ustar 00000000000000[package] name = "time" version = "0.3.37" authors = ["Jacob Pratt ", "Time contributors"] edition = "2021" rust-version = "1.67.1" repository = "https://github.com/time-rs/time" homepage = "https://time-rs.github.io" keywords = ["date", "time", "calendar", "duration"] categories = [ "date-and-time", "no-std", "parser-implementations", "value-formatting", ] readme = "../README.md" license = "MIT OR Apache-2.0" description = "Date and time library. Fully interoperable with the standard library. Mostly compatible with #![no_std]." include = ["src/**/*", "LICENSE-*", "README.md"] [lib] bench = false [lints] workspace = true [package.metadata.docs.rs] all-features = true targets = ["x86_64-unknown-linux-gnu"] rustdoc-args = ["--generate-link-to-definition"] [features] default = ["std"] alloc = ["serde?/alloc"] 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", "deranged/quickcheck"] rand = ["dep:rand", "deranged/rand"] serde = ["dep:serde", "time-macros?/serde", "deranged/serde"] serde-human-readable = ["serde", "formatting", "parsing"] # Deprecated in favor of using the relevant flags directly. serde-well-known = ["serde", "formatting", "parsing"] std = ["alloc", "deranged/std"] wasm-bindgen = ["dep:js-sys"] # If adding an optional dependency, be sure to use the `dep:` prefix above to avoid an implicit # feature gate. [dependencies] deranged = { workspace = true } itoa = { workspace = true, optional = true } num-conv = { workspace = true } powerfmt = { workspace = true } quickcheck = { workspace = true, optional = true } rand = { workspace = true, optional = true } serde = { workspace = true, optional = true } time-core = { workspace = true } time-macros = { workspace = true, optional = true } [target.'cfg(target_family = "unix")'.dependencies] libc = { workspace = true, optional = true } num_threads = { workspace = true, optional = true } [target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] js-sys = { workspace = true, optional = true } [dev-dependencies] num-conv = { workspace = true } rand = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } serde_test = { workspace = true } quickcheck_macros = { workspace = true } time-macros = { workspace = true } rstest = { workspace = true } rstest_reuse = { workspace = true } [target.'cfg(__ui_tests)'.dev-dependencies] trybuild = { workspace = true } [target.'cfg(bench)'.dev-dependencies] criterion = { workspace = true } # Significant contributions to the benchmarks provided by Emil Lundberg. [[bench]] name = "benchmarks" harness = false path = "../benchmarks/main.rs" [[test]] name = "tests" path = "../tests/main.rs" time-0.3.37/LICENSE-Apache000064400000000000000000000261251046102023000130460ustar 00000000000000 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 2024 Jacob Pratt et al. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 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. time-0.3.37/LICENSE-MIT000064400000000000000000000020461046102023000123120ustar 00000000000000Copyright (c) 2024 Jacob Pratt et al. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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. time-0.3.37/README.md000064400000000000000000000050021046102023000121300ustar 00000000000000# time [![minimum rustc: 1.67.1](https://img.shields.io/badge/minimum%20rustc-1.67.1-yellowgreen?logo=rust&style=flat-square)](https://www.whatrustisit.com) [![version](https://img.shields.io/crates/v/time?color=blue&logo=rust&style=flat-square)](https://crates.io/crates/time) [![build status](https://img.shields.io/github/actions/workflow/status/time-rs/time/build.yaml?branch=main&style=flat-square)](https://github.com/time-rs/time/actions) [![codecov](https://codecov.io/gh/time-rs/time/branch/main/graph/badge.svg?token=yt4XSmQNKQ)](https://codecov.io/gh/time-rs/time) Documentation: - [latest release](https://docs.rs/time) - [main branch](https://time-rs.github.io/api/time) - [book](https://time-rs.github.io/book) ## Minimum Rust version policy `time` is guaranteed to compile with the latest stable release of Rust in addition to the two prior minor releases. For example, if the latest stable Rust release is 1.70, then `time` is guaranteed to compile with Rust 1.68, 1.69, and 1.70. The minimum supported Rust version may be increased to one of the aforementioned versions if doing so provides the end user a benefit. However, the minimum supported Rust version may also be bumped to a version four minor releases prior to the most recent stable release if doing so improves code quality or maintainability. For interoperability with third-party crates, it is guaranteed that there exists a version of that crate that supports the minimum supported Rust version of `time`. This does not mean that the latest version of the third-party crate supports the minimum supported Rust version of `time`. ## Contributing Contributions are always welcome! If you have an idea, it's best to float it by me before working on it to ensure no effort is wasted. If there's already an open issue for it, knock yourself out. Internal documentation can be viewed [here](https://time-rs.github.io/internal-api/time). If you have any questions, feel free to use [Discussions]. Don't hesitate to ask questions — that's what I'm here for! [Discussions]: https://github.com/time-rs/time/discussions ## License This project is licensed under either of - [Apache License, Version 2.0](https://github.com/time-rs/time/blob/main/LICENSE-Apache) - [MIT license](https://github.com/time-rs/time/blob/main/LICENSE-MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in time by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. time-0.3.37/src/date.rs000064400000000000000000001413401046102023000127310ustar 00000000000000//! The [`Date`] struct and its associated `impl`s. #[cfg(feature = "formatting")] use alloc::string::String; use core::num::NonZeroI32; use core::ops::{Add, Sub}; use core::time::Duration as StdDuration; use core::{cmp, fmt}; #[cfg(feature = "formatting")] use std::io; use deranged::RangedI32; use num_conv::prelude::*; use powerfmt::ext::FormatterExt; use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay}; use crate::convert::*; use crate::ext::DigitCount; #[cfg(feature = "formatting")] use crate::formatting::Formattable; use crate::internal_macros::{ cascade, const_try, const_try_opt, div_floor, ensure_ranged, expect_opt, impl_add_assign, impl_sub_assign, }; #[cfg(feature = "parsing")] use crate::parsing::Parsable; use crate::util::{days_in_year, is_leap_year, weeks_in_year}; use crate::{error, Duration, Month, PrimitiveDateTime, Time, Weekday}; type Year = RangedI32; /// 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: NonZeroI32, } impl Date { /// The minimum valid `Date`. /// /// The value of this may vary depending on the feature flags enabled. // Safety: `ordinal` is not zero. #[allow(clippy::undocumented_unsafe_blocks)] pub const MIN: Self = unsafe { Self::__from_ordinal_date_unchecked(MIN_YEAR, 1) }; /// The maximum valid `Date`. /// /// The value of this may vary depending on the feature flags enabled. // Safety: `ordinal` is not zero. #[allow(clippy::undocumented_unsafe_blocks)] pub const MAX: Self = unsafe { Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR)) }; // region: constructors /// Construct a `Date` from the year and ordinal values, the validity of which must be /// guaranteed by the caller. /// /// # Safety /// /// `ordinal` must not be zero. `year` should be in the range `MIN_YEAR..=MAX_YEAR`, but this /// is not a safety invariant. #[doc(hidden)] pub const unsafe fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self { debug_assert!(year >= MIN_YEAR); debug_assert!(year <= MAX_YEAR); debug_assert!(ordinal != 0); debug_assert!(ordinal <= days_in_year(year)); Self { // Safety: The caller must guarantee that `ordinal` is not zero. value: unsafe { NonZeroI32::new_unchecked((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 { /// 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_ranged!(Year: year); match day { 1..=28 => {} 29..=31 if day <= month.length(year) => {} _ => { return Err(error::ComponentRange { name: "day", minimum: 1, maximum: month.length(year) as _, value: day as _, conditional_range: true, }); } } // Safety: `ordinal` is not zero. Ok(unsafe { Self::__from_ordinal_date_unchecked( year, DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1] + day as u16, ) }) } /// Attempt to create a `Date` from the year and ordinal day number. /// /// ```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 { ensure_ranged!(Year: year); match ordinal { 1..=365 => {} 366 if is_leap_year(year) => {} _ => { return Err(error::ComponentRange { name: "ordinal", minimum: 1, maximum: days_in_year(year) as _, value: ordinal as _, conditional_range: true, }); } } // Safety: `ordinal` is not zero. Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) }) } /// Attempt to create a `Date` from the ISO year, week, and weekday. /// /// ```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 { ensure_ranged!(Year: year); match week { 1..=52 => {} 53 if week <= weeks_in_year(year) => {} _ => { return Err(error::ComponentRange { name: "week", minimum: 1, maximum: weeks_in_year(year) as _, value: week as _, conditional_range: true, }); } } let adj_year = year - 1; let raw = 365 * adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100) + 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 { // Safety: `ordinal` is not zero. unsafe { Self::__from_ordinal_date_unchecked( year - 1, (ordinal as u16).wrapping_add(days_in_year(year - 1)), ) } } else if ordinal > days_in_year(year) as i16 { // Safety: `ordinal` is not zero. unsafe { Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year)) } } else { // Safety: `ordinal` is not zero. unsafe { 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 { type JulianDay = RangedI32<{ Date::MIN.to_julian_day() }, { Date::MAX.to_julian_day() }>; ensure_ranged!(JulianDay: julian_day); Ok(Self::from_julian_day_unchecked(julian_day)) } /// 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); } // Safety: `ordinal` is not zero. unsafe { 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.get() >> 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.get() & 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 { if self.ordinal() == 366 || (self.ordinal() == 365 && !is_leap_year(self.year())) { if self.value.get() == Self::MAX.value.get() { None } else { // Safety: `ordinal` is not zero. unsafe { Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1)) } } } else { Some(Self { // Safety: `ordinal` is not zero. value: unsafe { NonZeroI32::new_unchecked(self.value.get() + 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 { if self.ordinal() != 1 { Some(Self { // Safety: `ordinal` is not zero. value: unsafe { NonZeroI32::new_unchecked(self.value.get() - 1) }, }) } else if self.value.get() == Self::MIN.value.get() { None } else { // Safety: `ordinal` is not zero. Some(unsafe { Self::__from_ordinal_date_unchecked(self.year() - 1, days_in_year(self.year() - 1)) }) } } /// Calculates the first occurrence of a weekday that is strictly later than a given `Date`. /// /// # Panics /// Panics if an overflow occurred. /// /// # Examples /// ``` /// # use time::Weekday; /// # use time_macros::date; /// assert_eq!( /// date!(2023-06-28).next_occurrence(Weekday::Monday), /// date!(2023-07-03) /// ); /// assert_eq!( /// date!(2023-06-19).next_occurrence(Weekday::Monday), /// date!(2023-06-26) /// ); /// ``` pub const fn next_occurrence(self, weekday: Weekday) -> Self { expect_opt!( self.checked_next_occurrence(weekday), "overflow calculating the next occurrence of a weekday" ) } /// Calculates the first occurrence of a weekday that is strictly earlier than a given `Date`. /// /// # Panics /// Panics if an overflow occurred. /// /// # Examples /// ``` /// # use time::Weekday; /// # use time_macros::date; /// assert_eq!( /// date!(2023-06-28).prev_occurrence(Weekday::Monday), /// date!(2023-06-26) /// ); /// assert_eq!( /// date!(2023-06-19).prev_occurrence(Weekday::Monday), /// date!(2023-06-12) /// ); /// ``` pub const fn prev_occurrence(self, weekday: Weekday) -> Self { expect_opt!( self.checked_prev_occurrence(weekday), "overflow calculating the previous occurrence of a weekday" ) } /// Calculates the `n`th occurrence of a weekday that is strictly later than a given `Date`. /// /// # Panics /// Panics if an overflow occurred or if `n == 0`. /// /// # Examples /// ``` /// # use time::Weekday; /// # use time_macros::date; /// assert_eq!( /// date!(2023-06-25).nth_next_occurrence(Weekday::Monday, 5), /// date!(2023-07-24) /// ); /// assert_eq!( /// date!(2023-06-26).nth_next_occurrence(Weekday::Monday, 5), /// date!(2023-07-31) /// ); /// ``` pub const fn nth_next_occurrence(self, weekday: Weekday, n: u8) -> Self { expect_opt!( self.checked_nth_next_occurrence(weekday, n), "overflow calculating the next occurrence of a weekday" ) } /// Calculates the `n`th occurrence of a weekday that is strictly earlier than a given `Date`. /// /// # Panics /// Panics if an overflow occurred or if `n == 0`. /// /// # Examples /// ``` /// # use time::Weekday; /// # use time_macros::date; /// assert_eq!( /// date!(2023-06-27).nth_prev_occurrence(Weekday::Monday, 3), /// date!(2023-06-12) /// ); /// assert_eq!( /// date!(2023-06-26).nth_prev_occurrence(Weekday::Monday, 3), /// date!(2023-06-05) /// ); /// ``` pub const fn nth_prev_occurrence(self, weekday: Weekday, n: u8) -> Self { expect_opt!( self.checked_nth_prev_occurrence(weekday, n), "overflow calculating the previous occurrence of a weekday" ) } /// Get the Julian day for the date. /// /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(-4713 - 11 - 24).to_julian_day(), 0); /// assert_eq!(date!(2000-01-01).to_julian_day(), 2_451_545); /// assert_eq!(date!(2019-01-01).to_julian_day(), 2_458_485); /// assert_eq!(date!(2019-12-31).to_julian_day(), 2_458_849); /// ``` pub const fn to_julian_day(self) -> i32 { let year = self.year() - 1; let ordinal = self.ordinal() as i32; ordinal + 365 * year + div_floor!(year, 4) - div_floor!(year, 100) + div_floor!(year, 400) + 1_721_425 } // endregion getters // region: checked arithmetic /// Computes `self + duration`, returning `None` if an overflow occurred. /// /// ```rust /// # use time::{Date, ext::NumericalDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.checked_add(1.days()), None); /// assert_eq!(Date::MIN.checked_add((-2).days()), None); /// assert_eq!( /// date!(2020-12-31).checked_add(2.days()), /// Some(date!(2021-01-02)) /// ); /// ``` /// /// # Note /// /// This function only takes whole days into account. /// /// ```rust /// # use time::{Date, ext::NumericalDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.checked_add(23.hours()), Some(Date::MAX)); /// assert_eq!(Date::MIN.checked_add((-23).hours()), Some(Date::MIN)); /// assert_eq!( /// date!(2020-12-31).checked_add(23.hours()), /// Some(date!(2020-12-31)) /// ); /// assert_eq!( /// date!(2020-12-31).checked_add(47.hours()), /// Some(date!(2021-01-01)) /// ); /// ``` pub const fn checked_add(self, duration: Duration) -> Option { 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. /// /// ```rust /// # use time::{Date, ext::NumericalStdDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.checked_add_std(1.std_days()), None); /// assert_eq!( /// date!(2020-12-31).checked_add_std(2.std_days()), /// Some(date!(2021-01-02)) /// ); /// ``` /// /// # Note /// /// This function only takes whole days into account. /// /// ```rust /// # use time::{Date, ext::NumericalStdDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.checked_add_std(23.std_hours()), Some(Date::MAX)); /// assert_eq!( /// date!(2020-12-31).checked_add_std(23.std_hours()), /// Some(date!(2020-12-31)) /// ); /// assert_eq!( /// date!(2020-12-31).checked_add_std(47.std_hours()), /// Some(date!(2021-01-01)) /// ); /// ``` pub const fn checked_add_std(self, duration: StdDuration) -> Option { let whole_days = duration.as_secs() / Second::per(Day) as u64; if whole_days > i32::MAX as u64 { return None; } let julian_day = const_try_opt!(self.to_julian_day().checked_add(whole_days as _)); if let Ok(date) = Self::from_julian_day(julian_day) { Some(date) } else { None } } /// Computes `self - duration`, returning `None` if an overflow occurred. /// /// ``` /// # 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 { 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 } } /// Computes `self - duration`, returning `None` if an overflow occurred. /// /// ``` /// # use time::{Date, ext::NumericalStdDuration}; /// # use time_macros::date; /// assert_eq!(Date::MIN.checked_sub_std(1.std_days()), None); /// assert_eq!( /// date!(2020-12-31).checked_sub_std(2.std_days()), /// Some(date!(2020-12-29)) /// ); /// ``` /// /// # Note /// /// This function only takes whole days into account. /// /// ``` /// # use time::{Date, ext::NumericalStdDuration}; /// # use time_macros::date; /// assert_eq!(Date::MIN.checked_sub_std(23.std_hours()), Some(Date::MIN)); /// assert_eq!( /// date!(2020-12-31).checked_sub_std(23.std_hours()), /// Some(date!(2020-12-31)) /// ); /// assert_eq!( /// date!(2020-12-31).checked_sub_std(47.std_hours()), /// Some(date!(2020-12-30)) /// ); /// ``` pub const fn checked_sub_std(self, duration: StdDuration) -> Option { let whole_days = duration.as_secs() / Second::per(Day) as u64; if whole_days > i32::MAX as u64 { return None; } let julian_day = const_try_opt!(self.to_julian_day().checked_sub(whole_days as _)); if let Ok(date) = Self::from_julian_day(julian_day) { Some(date) } else { None } } /// Calculates the first occurrence of a weekday that is strictly later than a given `Date`. /// Returns `None` if an overflow occurred. pub(crate) const fn checked_next_occurrence(self, weekday: Weekday) -> Option { let day_diff = match weekday as i8 - self.weekday() as i8 { 1 | -6 => 1, 2 | -5 => 2, 3 | -4 => 3, 4 | -3 => 4, 5 | -2 => 5, 6 | -1 => 6, val => { debug_assert!(val == 0); 7 } }; self.checked_add(Duration::days(day_diff)) } /// Calculates the first occurrence of a weekday that is strictly earlier than a given `Date`. /// Returns `None` if an overflow occurred. pub(crate) const fn checked_prev_occurrence(self, weekday: Weekday) -> Option { let day_diff = match weekday as i8 - self.weekday() as i8 { 1 | -6 => 6, 2 | -5 => 5, 3 | -4 => 4, 4 | -3 => 3, 5 | -2 => 2, 6 | -1 => 1, val => { debug_assert!(val == 0); 7 } }; self.checked_sub(Duration::days(day_diff)) } /// Calculates the `n`th occurrence of a weekday that is strictly later than a given `Date`. /// Returns `None` if an overflow occurred or if `n == 0`. pub(crate) const fn checked_nth_next_occurrence(self, weekday: Weekday, n: u8) -> Option { if n == 0 { return None; } const_try_opt!(self.checked_next_occurrence(weekday)) .checked_add(Duration::weeks(n as i64 - 1)) } /// Calculates the `n`th occurrence of a weekday that is strictly earlier than a given `Date`. /// Returns `None` if an overflow occurred or if `n == 0`. pub(crate) const fn checked_nth_prev_occurrence(self, weekday: Weekday, n: u8) -> Option { if n == 0 { return None; } const_try_opt!(self.checked_prev_occurrence(weekday)) .checked_sub(Duration::weeks(n as i64 - 1)) } // 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 { ensure_ranged!(Year: year); let ordinal = self.ordinal(); // Dates in January and February are unaffected by leap years. if ordinal <= 59 { // Safety: `ordinal` is not zero. return Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) }); } match (is_leap_year(self.year()), is_leap_year(year)) { (false, false) | (true, true) => { // Safety: `ordinal` is not zero. Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) }) } // February 29 does not exist in common years. (true, false) if ordinal == 60 => Err(error::ComponentRange { name: "day", 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. // Safety: `ordinal` is not zero. (false, true) => Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal + 1) }), // We're going from a leap year to a common year. Shift dates in January and // February by one day. // Safety: `ordinal` is not zero. (true, false) => Ok(unsafe { 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 { 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 { match day { 1..=28 => {} 29..=31 if day <= self.month().length(self.year()) => {} _ => { return Err(error::ComponentRange { name: "day", minimum: 1, maximum: self.month().length(self.year()) as _, value: day as _, conditional_range: true, }); } } // Safety: `ordinal` is not zero. Ok(unsafe { Self::__from_ordinal_date_unchecked( self.year(), (self.ordinal() as i16 - self.day() as i16 + day as i16) as _, ) }) } /// Replace the day of the year. /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(2022-049).replace_ordinal(1), Ok(date!(2022-001))); /// assert!(date!(2022-049).replace_ordinal(0).is_err()); // 0 isn't a valid ordinal /// assert!(date!(2022-049).replace_ordinal(366).is_err()); // 2022 isn't a leap year /// ```` #[must_use = "This method does not mutate the original `Date`."] pub const fn replace_ordinal(self, ordinal: u16) -> Result { match ordinal { 1..=365 => {} 366 if is_leap_year(self.year()) => {} _ => { return Err(error::ComponentRange { name: "ordinal", minimum: 1, maximum: days_in_year(self.year()) as _, value: ordinal as _, conditional_range: true, }); } } // Safety: `ordinal` is in range. Ok(unsafe { Self::__from_ordinal_date_unchecked(self.year(), ordinal) }) } // endregion replacement } // 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 { 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 { 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 { 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 { 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 { 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 { 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 { description.parse_date(input.as_bytes()) } } mod private { #[non_exhaustive] #[derive(Debug, Clone, Copy)] pub struct DateMetadata { /// The width of the year component, including the sign. pub(super) year_width: u8, /// Whether the sign should be displayed. pub(super) display_sign: bool, pub(super) year: i32, pub(super) month: u8, pub(super) day: u8, } } use private::DateMetadata; impl SmartDisplay for Date { type Metadata = DateMetadata; fn metadata(&self, _: FormatterOptions) -> Metadata { let (year, month, day) = self.to_calendar_date(); // There is a minimum of four digits for any year. let mut year_width = cmp::max(year.unsigned_abs().num_digits(), 4); let display_sign = if !(0..10_000).contains(&year) { // An extra character is required for the sign. year_width += 1; true } else { false }; let formatted_width = year_width.extend::() + smart_display::padded_width_of!( "-", u8::from(month) => width(2), "-", day => width(2), ); Metadata::new( formatted_width, self, DateMetadata { year_width, display_sign, year, month: u8::from(month), day, }, ) } fn fmt_with_metadata( &self, f: &mut fmt::Formatter<'_>, metadata: Metadata, ) -> fmt::Result { let DateMetadata { year_width, display_sign, year, month, day, } = *metadata; let year_width = year_width.extend(); if display_sign { f.pad_with_width( metadata.unpadded_width(), format_args!("{year:+0year_width$}-{month:02}-{day:02}"), ) } else { f.pad_with_width( metadata.unpadded_width(), format_args!("{year:0year_width$}-{month:02}-{day:02}"), ) } } } impl fmt::Display for Date { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { SmartDisplay::fmt(self, f) } } impl fmt::Debug for Date { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fmt::Display::fmt(self, f) } } // endregion formatting & parsing // region: trait impls impl Add for Date { type Output = Self; /// # Panics /// /// This may panic if an overflow occurs. fn add(self, duration: Duration) -> Self::Output { self.checked_add(duration) .expect("overflow adding duration to date") } } impl Add for Date { type Output = Self; /// # Panics /// /// This may panic if an overflow occurs. fn add(self, duration: StdDuration) -> Self::Output { self.checked_add_std(duration) .expect("overflow adding duration to date") } } impl_add_assign!(Date: Duration, StdDuration); impl Sub for Date { type Output = Self; /// # Panics /// /// This may panic if an overflow occurs. fn sub(self, duration: Duration) -> Self::Output { self.checked_sub(duration) .expect("overflow subtracting duration from date") } } impl Sub for Date { type Output = Self; /// # Panics /// /// This may panic if an overflow occurs. fn sub(self, duration: StdDuration) -> Self::Output { self.checked_sub_std(duration) .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()).extend()) } } // endregion trait impls time-0.3.37/src/duration.rs000064400000000000000000001565061046102023000136530ustar 00000000000000//! 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 deranged::RangedI32; use num_conv::prelude::*; use crate::convert::*; use crate::error; use crate::internal_macros::{ const_try_opt, expect_opt, impl_add_assign, impl_div_assign, impl_mul_assign, impl_sub_assign, }; #[cfg(feature = "std")] #[allow(deprecated)] use crate::Instant; /// By explicitly inserting this enum where padding is expected, the compiler is able to better /// 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, } /// The type of the `nanosecond` field of `Duration`. type Nanoseconds = RangedI32<{ -(Nanosecond::per(Second) as i32 - 1) }, { Nanosecond::per(Second) as i32 - 1 }>; /// A span of time with nanosecond precision. /// /// 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, 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. // Sign must match that of `seconds` (though this is not a safety requirement). nanoseconds: Nanoseconds, #[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 Default for Duration { fn default() -> Self { Self { seconds: 0, nanoseconds: Nanoseconds::new_static::<0>(), padding: Padding::Optimize, } } } /// This is adapted from the [`std` implementation][std], which uses mostly bit /// operations to ensure the highest precision: /// /// Changes from `std` are marked and explained below. /// /// [std]: https://github.com/rust-lang/rust/blob/3a37c2f0523c87147b64f1b8099fc9df22e8c53e/library/core/src/time.rs#L1262-L1340 #[rustfmt::skip] // Skip `rustfmt` because it reformats the arguments of the macro weirdly. macro_rules! try_from_secs { ( secs = $secs: expr, mantissa_bits = $mant_bits: literal, exponent_bits = $exp_bits: literal, offset = $offset: literal, bits_ty = $bits_ty:ty, bits_ty_signed = $bits_ty_signed:ty, double_ty = $double_ty:ty, float_ty = $float_ty:ty, is_nan = $is_nan:expr, is_overflow = $is_overflow:expr, ) => {{ 'value: { const MIN_EXP: i16 = 1 - (1i16 << $exp_bits) / 2; const MANT_MASK: $bits_ty = (1 << $mant_bits) - 1; const EXP_MASK: $bits_ty = (1 << $exp_bits) - 1; // Change from std: No error check for negative values necessary. let bits = $secs.to_bits(); let mant = (bits & MANT_MASK) | (MANT_MASK + 1); let exp = ((bits >> $mant_bits) & EXP_MASK) as i16 + MIN_EXP; let (secs, nanos) = if exp < -31 { // the input represents less than 1ns and can not be rounded to it (0u64, 0u32) } else if exp < 0 { // the input is less than 1 second let t = <$double_ty>::from(mant) << ($offset + exp); let nanos_offset = $mant_bits + $offset; let nanos_tmp = u128::from(Nanosecond::per(Second)) * u128::from(t); let nanos = (nanos_tmp >> nanos_offset) as u32; let rem_mask = (1 << nanos_offset) - 1; let rem_msb_mask = 1 << (nanos_offset - 1); let rem = nanos_tmp & rem_mask; let is_tie = rem == rem_msb_mask; let is_even = (nanos & 1) == 0; let rem_msb = nanos_tmp & rem_msb_mask == 0; let add_ns = !(rem_msb || (is_even && is_tie)); // f32 does not have enough precision to trigger the second branch // since it can not represent numbers between 0.999_999_940_395 and 1.0. let nanos = nanos + add_ns as u32; if ($mant_bits == 23) || (nanos != Nanosecond::per(Second)) { (0, nanos) } else { (1, 0) } } else if exp < $mant_bits { let secs = u64::from(mant >> ($mant_bits - exp)); let t = <$double_ty>::from((mant << exp) & MANT_MASK); let nanos_offset = $mant_bits; let nanos_tmp = <$double_ty>::from(Nanosecond::per(Second)) * t; let nanos = (nanos_tmp >> nanos_offset) as u32; let rem_mask = (1 << nanos_offset) - 1; let rem_msb_mask = 1 << (nanos_offset - 1); let rem = nanos_tmp & rem_mask; let is_tie = rem == rem_msb_mask; let is_even = (nanos & 1) == 0; let rem_msb = nanos_tmp & rem_msb_mask == 0; let add_ns = !(rem_msb || (is_even && is_tie)); // f32 does not have enough precision to trigger the second branch. // For example, it can not represent numbers between 1.999_999_880... // and 2.0. Bigger values result in even smaller precision of the // fractional part. let nanos = nanos + add_ns as u32; if ($mant_bits == 23) || (nanos != Nanosecond::per(Second)) { (secs, nanos) } else { (secs + 1, 0) } } else if exp < 63 { // Change from std: The exponent here is 63 instead of 64, // because i64::MAX + 1 is 2^63. // the input has no fractional part let secs = u64::from(mant) << (exp - $mant_bits); (secs, 0) } else if bits == (i64::MIN as $float_ty).to_bits() { // Change from std: Signed integers are asymmetrical in that // iN::MIN is -iN::MAX - 1. So for example i8 covers the // following numbers -128..=127. The check above (exp < 63) // doesn't cover i64::MIN as that is -2^63, so we have this // additional case to handle the asymmetry of iN::MIN. break 'value Self::new_ranged_unchecked(i64::MIN, Nanoseconds::new_static::<0>()); } else if $secs.is_nan() { // Change from std: std doesn't differentiate between the error // cases. $is_nan } else { $is_overflow }; // Change from std: All the code is mostly unmodified in that it // simply calculates an unsigned integer. Here we extract the sign // bit and assign it to the number. We basically manually do two's // complement here, we could also use an if and just negate the // numbers based on the sign, but it turns out to be quite a bit // slower. let mask = (bits as $bits_ty_signed) >> ($mant_bits + $exp_bits); #[allow(trivial_numeric_casts)] let secs_signed = ((secs as i64) ^ (mask as i64)) - (mask as i64); #[allow(trivial_numeric_casts)] let nanos_signed = ((nanos as i32) ^ (mask as i32)) - (mask as i32); // Safety: `nanos_signed` is in range. unsafe { Self::new_unchecked(secs_signed, nanos_signed) } } }}; } impl Duration { // region: constants /// Equivalent to `0.seconds()`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::ZERO, 0.seconds()); /// ``` pub const ZERO: Self = Self::seconds(0); /// Equivalent to `1.nanoseconds()`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::NANOSECOND, 1.nanoseconds()); /// ``` pub const NANOSECOND: Self = Self::nanoseconds(1); /// Equivalent to `1.microseconds()`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::MICROSECOND, 1.microseconds()); /// ``` pub const MICROSECOND: Self = Self::microseconds(1); /// Equivalent to `1.milliseconds()`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::MILLISECOND, 1.milliseconds()); /// ``` pub const MILLISECOND: Self = Self::milliseconds(1); /// Equivalent to `1.seconds()`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::SECOND, 1.seconds()); /// ``` pub const SECOND: Self = Self::seconds(1); /// Equivalent to `1.minutes()`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::MINUTE, 1.minutes()); /// ``` pub const MINUTE: Self = Self::minutes(1); /// Equivalent to `1.hours()`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::HOUR, 1.hours()); /// ``` pub const HOUR: Self = Self::hours(1); /// Equivalent to `1.days()`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::DAY, 1.days()); /// ``` pub const DAY: Self = Self::days(1); /// Equivalent to `1.weeks()`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::WEEK, 1.weeks()); /// ``` pub const WEEK: Self = Self::weeks(1); /// The minimum possible duration. Adding any negative duration to this will cause an overflow. pub const MIN: Self = Self::new_ranged(i64::MIN, Nanoseconds::MIN); /// The maximum possible duration. Adding any positive duration to this will cause an overflow. pub const MAX: Self = Self::new_ranged(i64::MAX, Nanoseconds::MAX); // 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.get() == 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.get() < 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.get() > 0 } // endregion is_{sign} // region: abs /// Get the absolute value of the duration. /// /// This method saturates the returned value if it would otherwise overflow. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.seconds().abs(), 1.seconds()); /// assert_eq!(0.seconds().abs(), 0.seconds()); /// assert_eq!((-1).seconds().abs(), 1.seconds()); /// ``` pub const fn abs(self) -> Self { match self.seconds.checked_abs() { Some(seconds) => Self::new_ranged_unchecked(seconds, self.nanoseconds.abs()), None => Self::MAX, } } /// Convert the existing `Duration` to a `std::time::Duration` and its sign. This returns a /// [`std::time::Duration`] and does not saturate the returned value (unlike [`Duration::abs`]). /// /// ```rust /// # use time::ext::{NumericalDuration, NumericalStdDuration}; /// assert_eq!(1.seconds().unsigned_abs(), 1.std_seconds()); /// assert_eq!(0.seconds().unsigned_abs(), 0.std_seconds()); /// assert_eq!((-1).seconds().unsigned_abs(), 1.std_seconds()); /// ``` pub const fn unsigned_abs(self) -> StdDuration { StdDuration::new( self.seconds.unsigned_abs(), self.nanoseconds.get().unsigned_abs(), ) } // endregion abs // region: constructors /// Create a new `Duration` without checking the validity of the components. /// /// # Safety /// /// - `nanoseconds` must be in the range `-999_999_999..=999_999_999`. /// /// While the sign of `nanoseconds` is required to be the same as the sign of `seconds`, this is /// not a safety invariant. pub(crate) const unsafe fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self { Self::new_ranged_unchecked( seconds, // Safety: The caller must uphold the safety invariants. unsafe { Nanoseconds::new_unchecked(nanoseconds) }, ) } /// Create a new `Duration` without checking the validity of the components. pub(crate) const fn new_ranged_unchecked(seconds: i64, nanoseconds: Nanoseconds) -> Self { if seconds < 0 { debug_assert!(nanoseconds.get() <= 0); } else if seconds > 0 { debug_assert!(nanoseconds.get() >= 0); } Self { seconds, nanoseconds, padding: Padding::Optimize, } } /// Create a new `Duration` with the provided seconds and nanoseconds. If nanoseconds is at /// least ±109, 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()); /// ``` /// /// # Panics /// /// This may panic if an overflow occurs. pub const fn new(mut seconds: i64, mut nanoseconds: i32) -> Self { seconds = expect_opt!( seconds.checked_add(nanoseconds as i64 / Nanosecond::per(Second) as i64), "overflow constructing `time::Duration`" ); nanoseconds %= Nanosecond::per(Second) as i32; if seconds > 0 && nanoseconds < 0 { // `seconds` cannot overflow here because it is positive. seconds -= 1; nanoseconds += Nanosecond::per(Second) as i32; } else if seconds < 0 && nanoseconds > 0 { // `seconds` cannot overflow here because it is negative. seconds += 1; nanoseconds -= Nanosecond::per(Second) as i32; } // Safety: `nanoseconds` is in range due to the modulus above. unsafe { Self::new_unchecked(seconds, nanoseconds) } } /// Create a new `Duration` with the provided seconds and nanoseconds. pub(crate) const fn new_ranged(mut seconds: i64, mut nanoseconds: Nanoseconds) -> Self { if seconds > 0 && nanoseconds.get() < 0 { // `seconds` cannot overflow here because it is positive. seconds -= 1; // Safety: `nanoseconds` is negative with a maximum of 999,999,999, so adding a billion // to it is guaranteed to result in an in-range value. nanoseconds = unsafe { Nanoseconds::new_unchecked(nanoseconds.get() + Nanosecond::per(Second) as i32) }; } else if seconds < 0 && nanoseconds.get() > 0 { // `seconds` cannot overflow here because it is negative. seconds += 1; // Safety: `nanoseconds` is positive with a minimum of -999,999,999, so subtracting a // billion from it is guaranteed to result in an in-range value. nanoseconds = unsafe { Nanoseconds::new_unchecked(nanoseconds.get() - Nanosecond::per(Second) as i32) }; } Self::new_ranged_unchecked(seconds, nanoseconds) } /// Create a new `Duration` with the given number of weeks. Equivalent to /// `Duration::seconds(weeks * 604_800)`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::weeks(1), 604_800.seconds()); /// ``` /// /// # Panics /// /// This may panic if an overflow occurs. pub const fn weeks(weeks: i64) -> Self { Self::seconds(expect_opt!( weeks.checked_mul(Second::per(Week) as _), "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()); /// ``` /// /// # Panics /// /// This may panic if an overflow occurs. pub const fn days(days: i64) -> Self { Self::seconds(expect_opt!( days.checked_mul(Second::per(Day) as _), "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()); /// ``` /// /// # Panics /// /// This may panic if an overflow occurs. pub const fn hours(hours: i64) -> Self { Self::seconds(expect_opt!( hours.checked_mul(Second::per(Hour) as _), "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()); /// ``` /// /// # Panics /// /// This may panic if an overflow occurs. pub const fn minutes(minutes: i64) -> Self { Self::seconds(expect_opt!( minutes.checked_mul(Second::per(Minute) as _), "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_ranged_unchecked(seconds, Nanoseconds::new_static::<0>()) } /// Creates a new `Duration` from the specified number of seconds represented as `f64`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::seconds_f64(0.5), 0.5.seconds()); /// assert_eq!(Duration::seconds_f64(-0.5), (-0.5).seconds()); /// ``` pub fn seconds_f64(seconds: f64) -> Self { try_from_secs!( secs = seconds, mantissa_bits = 52, exponent_bits = 11, offset = 44, bits_ty = u64, bits_ty_signed = i64, double_ty = u128, float_ty = f64, is_nan = crate::expect_failed("passed NaN to `time::Duration::seconds_f64`"), is_overflow = crate::expect_failed("overflow constructing `time::Duration`"), ) } /// Creates a new `Duration` from the specified number of seconds represented as `f32`. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::seconds_f32(0.5), 0.5.seconds()); /// assert_eq!(Duration::seconds_f32(-0.5), (-0.5).seconds()); /// ``` pub fn seconds_f32(seconds: f32) -> Self { try_from_secs!( secs = seconds, mantissa_bits = 23, exponent_bits = 8, offset = 41, bits_ty = u32, bits_ty_signed = i32, double_ty = u64, float_ty = f32, is_nan = crate::expect_failed("passed NaN to `time::Duration::seconds_f32`"), is_overflow = crate::expect_failed("overflow constructing `time::Duration`"), ) } /// Creates a new `Duration` from the specified number of seconds /// represented as `f64`. Any values that are out of bounds are saturated at /// the minimum or maximum respectively. `NaN` gets turned into a `Duration` /// of 0 seconds. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::saturating_seconds_f64(0.5), 0.5.seconds()); /// assert_eq!(Duration::saturating_seconds_f64(-0.5), (-0.5).seconds()); /// assert_eq!( /// Duration::saturating_seconds_f64(f64::NAN), /// Duration::new(0, 0), /// ); /// assert_eq!( /// Duration::saturating_seconds_f64(f64::NEG_INFINITY), /// Duration::MIN, /// ); /// assert_eq!( /// Duration::saturating_seconds_f64(f64::INFINITY), /// Duration::MAX, /// ); /// ``` pub fn saturating_seconds_f64(seconds: f64) -> Self { try_from_secs!( secs = seconds, mantissa_bits = 52, exponent_bits = 11, offset = 44, bits_ty = u64, bits_ty_signed = i64, double_ty = u128, float_ty = f64, is_nan = return Self::ZERO, is_overflow = return if seconds < 0.0 { Self::MIN } else { Self::MAX }, ) } /// Creates a new `Duration` from the specified number of seconds /// represented as `f32`. Any values that are out of bounds are saturated at /// the minimum or maximum respectively. `NaN` gets turned into a `Duration` /// of 0 seconds. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::saturating_seconds_f32(0.5), 0.5.seconds()); /// assert_eq!(Duration::saturating_seconds_f32(-0.5), (-0.5).seconds()); /// assert_eq!( /// Duration::saturating_seconds_f32(f32::NAN), /// Duration::new(0, 0), /// ); /// assert_eq!( /// Duration::saturating_seconds_f32(f32::NEG_INFINITY), /// Duration::MIN, /// ); /// assert_eq!( /// Duration::saturating_seconds_f32(f32::INFINITY), /// Duration::MAX, /// ); /// ``` pub fn saturating_seconds_f32(seconds: f32) -> Self { try_from_secs!( secs = seconds, mantissa_bits = 23, exponent_bits = 8, offset = 41, bits_ty = u32, bits_ty_signed = i32, double_ty = u64, float_ty = f32, is_nan = return Self::ZERO, is_overflow = return if seconds < 0.0 { Self::MIN } else { Self::MAX }, ) } /// Creates a new `Duration` from the specified number of seconds /// represented as `f64`. Returns `None` if the `Duration` can't be /// represented. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::checked_seconds_f64(0.5), Some(0.5.seconds())); /// assert_eq!(Duration::checked_seconds_f64(-0.5), Some((-0.5).seconds())); /// assert_eq!(Duration::checked_seconds_f64(f64::NAN), None); /// assert_eq!(Duration::checked_seconds_f64(f64::NEG_INFINITY), None); /// assert_eq!(Duration::checked_seconds_f64(f64::INFINITY), None); /// ``` pub fn checked_seconds_f64(seconds: f64) -> Option { Some(try_from_secs!( secs = seconds, mantissa_bits = 52, exponent_bits = 11, offset = 44, bits_ty = u64, bits_ty_signed = i64, double_ty = u128, float_ty = f64, is_nan = return None, is_overflow = return None, )) } /// Creates a new `Duration` from the specified number of seconds /// represented as `f32`. Returns `None` if the `Duration` can't be /// represented. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::checked_seconds_f32(0.5), Some(0.5.seconds())); /// assert_eq!(Duration::checked_seconds_f32(-0.5), Some((-0.5).seconds())); /// assert_eq!(Duration::checked_seconds_f32(f32::NAN), None); /// assert_eq!(Duration::checked_seconds_f32(f32::NEG_INFINITY), None); /// assert_eq!(Duration::checked_seconds_f32(f32::INFINITY), None); /// ``` pub fn checked_seconds_f32(seconds: f32) -> Option { Some(try_from_secs!( secs = seconds, mantissa_bits = 23, exponent_bits = 8, offset = 41, bits_ty = u32, bits_ty_signed = i32, double_ty = u64, float_ty = f32, is_nan = return None, is_overflow = return None, )) } /// Create a new `Duration` with the given number of milliseconds. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(Duration::milliseconds(1), 1_000.microseconds()); /// assert_eq!(Duration::milliseconds(-1), (-1_000).microseconds()); /// ``` pub const fn milliseconds(milliseconds: i64) -> Self { // Safety: `nanoseconds` is guaranteed to be in range because of the modulus. unsafe { Self::new_unchecked( milliseconds / Millisecond::per(Second) as i64, (milliseconds % Millisecond::per(Second) as i64 * Nanosecond::per(Millisecond) as i64) as _, ) } } /// Create a new `Duration` with the given number of microseconds. /// /// ```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 { // Safety: `nanoseconds` is guaranteed to be in range because of the modulus. unsafe { Self::new_unchecked( microseconds / Microsecond::per(Second) as i64, (microseconds % Microsecond::per(Second) as i64 * Nanosecond::per(Microsecond) as i64) as _, ) } } /// Create a new `Duration` with the given number of nanoseconds. /// /// ```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 { // Safety: `nanoseconds` is guaranteed to be in range because of the modulus. unsafe { Self::new_unchecked( nanoseconds / Nanosecond::per(Second) as i64, (nanoseconds % Nanosecond::per(Second) as i64) as _, ) } } /// Create a new `Duration` with the given number of nanoseconds. /// /// As the input range cannot be fully mapped to the output, this should only be used where it's /// known to result in a valid value. pub(crate) const fn nanoseconds_i128(nanoseconds: i128) -> Self { let seconds = nanoseconds / Nanosecond::per(Second) as i128; let nanoseconds = nanoseconds % Nanosecond::per(Second) as i128; if seconds > i64::MAX as i128 || seconds < i64::MIN as i128 { crate::expect_failed("overflow constructing `time::Duration`"); } // Safety: `nanoseconds` is guaranteed to be in range because of the modulus above. unsafe { Self::new_unchecked(seconds as _, nanoseconds as _) } } // endregion constructors // region: getters /// Get the number of whole weeks in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.weeks().whole_weeks(), 1); /// assert_eq!((-1).weeks().whole_weeks(), -1); /// assert_eq!(6.days().whole_weeks(), 0); /// assert_eq!((-6).days().whole_weeks(), 0); /// ``` pub const fn whole_weeks(self) -> i64 { self.whole_seconds() / Second::per(Week) as i64 } /// Get the number of whole days in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.days().whole_days(), 1); /// assert_eq!((-1).days().whole_days(), -1); /// assert_eq!(23.hours().whole_days(), 0); /// assert_eq!((-23).hours().whole_days(), 0); /// ``` pub const fn whole_days(self) -> i64 { self.whole_seconds() / Second::per(Day) as i64 } /// Get the number of whole hours in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.hours().whole_hours(), 1); /// assert_eq!((-1).hours().whole_hours(), -1); /// assert_eq!(59.minutes().whole_hours(), 0); /// assert_eq!((-59).minutes().whole_hours(), 0); /// ``` pub const fn whole_hours(self) -> i64 { self.whole_seconds() / Second::per(Hour) as i64 } /// Get the number of whole minutes in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.minutes().whole_minutes(), 1); /// assert_eq!((-1).minutes().whole_minutes(), -1); /// assert_eq!(59.seconds().whole_minutes(), 0); /// assert_eq!((-59).seconds().whole_minutes(), 0); /// ``` pub const fn whole_minutes(self) -> i64 { self.whole_seconds() / Second::per(Minute) as i64 } /// Get the number of whole seconds in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.seconds().whole_seconds(), 1); /// assert_eq!((-1).seconds().whole_seconds(), -1); /// assert_eq!(1.minutes().whole_seconds(), 60); /// assert_eq!((-1).minutes().whole_seconds(), -60); /// ``` pub const fn whole_seconds(self) -> i64 { self.seconds } /// Get the number of fractional seconds in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.5.seconds().as_seconds_f64(), 1.5); /// assert_eq!((-1.5).seconds().as_seconds_f64(), -1.5); /// ``` pub fn as_seconds_f64(self) -> f64 { self.seconds as f64 + self.nanoseconds.get() as f64 / Nanosecond::per(Second) as f64 } /// Get the number of fractional seconds in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.5.seconds().as_seconds_f32(), 1.5); /// assert_eq!((-1.5).seconds().as_seconds_f32(), -1.5); /// ``` pub fn as_seconds_f32(self) -> f32 { self.seconds as f32 + self.nanoseconds.get() as f32 / Nanosecond::per(Second) as f32 } /// Get the number of whole milliseconds in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.seconds().whole_milliseconds(), 1_000); /// assert_eq!((-1).seconds().whole_milliseconds(), -1_000); /// assert_eq!(1.milliseconds().whole_milliseconds(), 1); /// assert_eq!((-1).milliseconds().whole_milliseconds(), -1); /// ``` pub const fn whole_milliseconds(self) -> i128 { self.seconds as i128 * Millisecond::per(Second) as i128 + self.nanoseconds.get() as i128 / Nanosecond::per(Millisecond) as i128 } /// Get the number of milliseconds past the number of whole seconds. /// /// Always in the range `-999..=999`. /// /// ```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.get() / Nanosecond::per(Millisecond) as i32) as _ } /// Get the number of whole microseconds in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.milliseconds().whole_microseconds(), 1_000); /// assert_eq!((-1).milliseconds().whole_microseconds(), -1_000); /// assert_eq!(1.microseconds().whole_microseconds(), 1); /// assert_eq!((-1).microseconds().whole_microseconds(), -1); /// ``` pub const fn whole_microseconds(self) -> i128 { self.seconds as i128 * Microsecond::per(Second) as i128 + self.nanoseconds.get() as i128 / Nanosecond::per(Microsecond) as i128 } /// Get the number of microseconds past the number of whole seconds. /// /// Always in the range `-999_999..=999_999`. /// /// ```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.get() / Nanosecond::per(Microsecond) as i32 } /// Get the number of nanoseconds in the duration. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(1.microseconds().whole_nanoseconds(), 1_000); /// assert_eq!((-1).microseconds().whole_nanoseconds(), -1_000); /// assert_eq!(1.nanoseconds().whole_nanoseconds(), 1); /// assert_eq!((-1).nanoseconds().whole_nanoseconds(), -1); /// ``` pub const fn whole_nanoseconds(self) -> i128 { self.seconds as i128 * Nanosecond::per(Second) as i128 + self.nanoseconds.get() as i128 } /// Get the number of nanoseconds past the number of whole seconds. /// /// The returned value will always be in the range `-999_999_999..=999_999_999`. /// /// ```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.get() } /// Get the number of nanoseconds past the number of whole seconds. #[cfg(feature = "quickcheck")] pub(crate) const fn subsec_nanoseconds_ranged(self) -> Nanoseconds { self.nanoseconds } // endregion getters // 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 { let mut seconds = const_try_opt!(self.seconds.checked_add(rhs.seconds)); let mut nanoseconds = self.nanoseconds.get() + rhs.nanoseconds.get(); if nanoseconds >= Nanosecond::per(Second) as _ || seconds < 0 && nanoseconds > 0 { nanoseconds -= Nanosecond::per(Second) as i32; seconds = const_try_opt!(seconds.checked_add(1)); } else if nanoseconds <= -(Nanosecond::per(Second) as i32) || seconds > 0 && nanoseconds < 0 { nanoseconds += Nanosecond::per(Second) as i32; seconds = const_try_opt!(seconds.checked_sub(1)); } // Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling. unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) } } /// Computes `self - rhs`, returning `None` if an overflow occurred. /// /// ```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 { let mut seconds = const_try_opt!(self.seconds.checked_sub(rhs.seconds)); let mut nanoseconds = self.nanoseconds.get() - rhs.nanoseconds.get(); if nanoseconds >= Nanosecond::per(Second) as _ || seconds < 0 && nanoseconds > 0 { nanoseconds -= Nanosecond::per(Second) as i32; seconds = const_try_opt!(seconds.checked_add(1)); } else if nanoseconds <= -(Nanosecond::per(Second) as i32) || seconds > 0 && nanoseconds < 0 { nanoseconds += Nanosecond::per(Second) as i32; seconds = const_try_opt!(seconds.checked_sub(1)); } // Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling. unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) } } /// Computes `self * rhs`, returning `None` if an overflow occurred. /// /// ```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 { // Multiply nanoseconds as i64, because it cannot overflow that way. let total_nanos = self.nanoseconds.get() as i64 * rhs as i64; let extra_secs = total_nanos / Nanosecond::per(Second) as i64; let nanoseconds = (total_nanos % Nanosecond::per(Second) as i64) as _; let seconds = const_try_opt!( const_try_opt!(self.seconds.checked_mul(rhs as _)).checked_add(extra_secs) ); // Safety: `nanoseconds` is guaranteed to be in range because of the modulus above. unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) } } /// Computes `self / rhs`, returning `None` if `rhs == 0` or if the result would overflow. /// /// ```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 { let (secs, extra_secs) = ( const_try_opt!(self.seconds.checked_div(rhs as i64)), self.seconds % (rhs as i64), ); let (mut nanos, extra_nanos) = (self.nanoseconds.get() / rhs, self.nanoseconds.get() % rhs); nanos += ((extra_secs * (Nanosecond::per(Second) as i64) + extra_nanos as i64) / (rhs as i64)) as i32; // Safety: `nanoseconds` is in range. unsafe { Some(Self::new_unchecked(secs, nanos)) } } /// Computes `-self`, returning `None` if the result would overflow. /// /// ```rust /// # use time::ext::NumericalDuration; /// # use time::Duration; /// assert_eq!(5.seconds().checked_neg(), Some((-5).seconds())); /// assert_eq!(Duration::MIN.checked_neg(), None); /// ``` pub const fn checked_neg(self) -> Option { if self.seconds == i64::MIN { None } else { Some(Self::new_ranged_unchecked( -self.seconds, self.nanoseconds.neg(), )) } } // 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.get() + rhs.nanoseconds.get(); if nanoseconds >= Nanosecond::per(Second) as _ || seconds < 0 && nanoseconds > 0 { nanoseconds -= Nanosecond::per(Second) as i32; seconds = match seconds.checked_add(1) { Some(seconds) => seconds, None => return Self::MAX, }; } else if nanoseconds <= -(Nanosecond::per(Second) as i32) || seconds > 0 && nanoseconds < 0 { nanoseconds += Nanosecond::per(Second) as i32; seconds = match seconds.checked_sub(1) { Some(seconds) => seconds, None => return Self::MIN, }; } // Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling. unsafe { Self::new_unchecked(seconds, nanoseconds) } } /// Computes `self - rhs`, saturating if an overflow occurred. /// /// ```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.get() - rhs.nanoseconds.get(); if nanoseconds >= Nanosecond::per(Second) as _ || seconds < 0 && nanoseconds > 0 { nanoseconds -= Nanosecond::per(Second) as i32; seconds = match seconds.checked_add(1) { Some(seconds) => seconds, None => return Self::MAX, }; } else if nanoseconds <= -(Nanosecond::per(Second) as i32) || seconds > 0 && nanoseconds < 0 { nanoseconds += Nanosecond::per(Second) as i32; seconds = match seconds.checked_sub(1) { Some(seconds) => seconds, None => return Self::MIN, }; } // Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling. unsafe { Self::new_unchecked(seconds, nanoseconds) } } /// Computes `self * rhs`, saturating if an overflow occurred. /// /// ```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.get() as i64 * rhs as i64; let extra_secs = total_nanos / Nanosecond::per(Second) as i64; let nanoseconds = (total_nanos % Nanosecond::per(Second) as i64) as _; let (seconds, overflow1) = self.seconds.overflowing_mul(rhs as _); if overflow1 { if self.seconds > 0 && rhs > 0 || self.seconds < 0 && rhs < 0 { 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; } // Safety: `nanoseconds` is guaranteed to be in range because of to the modulus above. unsafe { Self::new_unchecked(seconds, nanoseconds) } } // endregion saturating arithmetic /// Runs a closure, returning the duration of time it took to run. The return value of the /// closure is provided in the second part of the tuple. #[doc(hidden)] #[cfg(feature = "std")] #[deprecated( since = "0.3.32", note = "extremely limited use case, not intended for benchmarking" )] #[allow(deprecated)] pub fn time_fn(f: impl FnOnce() -> T) -> (Self, T) { let start = Instant::now(); let return_value = f(); let end = Instant::now(); (end - start, return_value) } } // region: trait impls /// The format returned by this implementation is not stable and must not be relied upon. /// /// By default this produces an exact, full-precision printout of the duration. /// For a concise, rounded printout instead, you can use the `.N` format specifier: /// /// ``` /// # use time::Duration; /// # /// let duration = Duration::new(123456, 789011223); /// println!("{duration:.3}"); /// ``` /// /// For the purposes of this implementation, a day is exactly 24 hours and a minute is exactly 60 /// seconds. impl fmt::Display for Duration { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_negative() { f.write_str("-")?; } if let Some(_precision) = f.precision() { // Concise, rounded representation. if self.is_zero() { // Write a zero value with the requested precision. return (0.).fmt(f).and_then(|_| f.write_str("s")); } /// Format the first item that produces a value greater than 1 and then break. macro_rules! item { ($name:literal, $value:expr) => { let value = $value; if value >= 1.0 { return value.fmt(f).and_then(|_| f.write_str($name)); } }; } // Even if this produces a de-normal float, because we're rounding we don't really care. let seconds = self.unsigned_abs().as_secs_f64(); item!("d", seconds / Second::per(Day) as f64); item!("h", seconds / Second::per(Hour) as f64); item!("m", seconds / Second::per(Minute) as f64); item!("s", seconds); item!("ms", seconds * Millisecond::per(Second) as f64); item!("µs", seconds * Microsecond::per(Second) as f64); item!("ns", seconds * Nanosecond::per(Second) as f64); } else { // Precise, but verbose representation. if self.is_zero() { return f.write_str("0s"); } /// Format a single item. macro_rules! item { ($name:literal, $value:expr) => { match $value { 0 => Ok(()), value => value.fmt(f).and_then(|_| f.write_str($name)), } }; } let seconds = self.seconds.unsigned_abs(); let nanoseconds = self.nanoseconds.get().unsigned_abs(); item!("d", seconds / Second::per(Day).extend::())?; item!( "h", seconds / Second::per(Hour).extend::() % Hour::per(Day).extend::() )?; item!( "m", seconds / Second::per(Minute).extend::() % Minute::per(Hour).extend::() )?; item!("s", seconds % Second::per(Minute).extend::())?; item!("ms", nanoseconds / Nanosecond::per(Millisecond))?; item!( "µs", nanoseconds / Nanosecond::per(Microsecond).extend::() % Microsecond::per(Millisecond).extend::() )?; item!( "ns", nanoseconds % Nanosecond::per(Microsecond).extend::() )?; } Ok(()) } } impl TryFrom for Duration { type Error = error::ConversionRange; fn try_from(original: StdDuration) -> Result { Ok(Self::new( original .as_secs() .try_into() .map_err(|_| error::ConversionRange)?, original.subsec_nanos().cast_signed(), )) } } impl TryFrom for StdDuration { type Error = error::ConversionRange; fn try_from(duration: Duration) -> Result { Ok(Self::new( duration .seconds .try_into() .map_err(|_| error::ConversionRange)?, duration .nanoseconds .get() .try_into() .map_err(|_| error::ConversionRange)?, )) } } impl Add for Duration { type Output = Self; /// # Panics /// /// This may panic if an overflow occurs. fn add(self, rhs: Self) -> Self::Output { self.checked_add(rhs) .expect("overflow when adding durations") } } impl Add for Duration { type Output = Self; /// # Panics /// /// This may panic if an overflow occurs. fn add(self, std_duration: StdDuration) -> Self::Output { self + Self::try_from(std_duration) .expect("overflow converting `std::time::Duration` to `time::Duration`") } } impl Add for StdDuration { type Output = Duration; fn add(self, rhs: Duration) -> Self::Output { rhs + self } } impl_add_assign!(Duration: Self, StdDuration); impl AddAssign for StdDuration { /// # Panics /// /// This may panic if the resulting addition cannot be represented. fn add_assign(&mut self, rhs: Duration) { *self = (*self + rhs).try_into().expect( "Cannot represent a resulting duration in std. Try `let x = x + rhs;`, which will \ change the type.", ); } } impl Neg for Duration { type Output = Self; fn neg(self) -> Self::Output { self.checked_neg().expect("overflow when negating duration") } } impl Sub for Duration { type Output = Self; /// # Panics /// /// This may panic if an overflow occurs. fn sub(self, rhs: Self) -> Self::Output { self.checked_sub(rhs) .expect("overflow when subtracting durations") } } impl Sub for Duration { type Output = Self; /// # Panics /// /// This may panic if an overflow occurs. fn sub(self, rhs: StdDuration) -> Self::Output { self - Self::try_from(rhs) .expect("overflow converting `std::time::Duration` to `time::Duration`") } } impl Sub for StdDuration { type Output = Duration; /// # Panics /// /// This may panic if an overflow occurs. fn sub(self, rhs: Duration) -> Self::Output { Duration::try_from(self) .expect("overflow converting `std::time::Duration` to `time::Duration`") - rhs } } impl_sub_assign!(Duration: Self, StdDuration); impl SubAssign for StdDuration { /// # Panics /// /// This may panic if the resulting subtraction can not be represented. fn sub_assign(&mut self, rhs: Duration) { *self = (*self - rhs).try_into().expect( "Cannot represent a resulting duration in std. Try `let x = x - rhs;`, which will \ 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.cast_signed().extend::()) .expect("overflow when multiplying duration") ) } } impl Mul 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.cast_signed().extend::() ) } } )+}; } duration_mul_div_int![i8, i16, i32, u8, u16, u32]; impl Mul for Duration { type Output = Self; fn mul(self, rhs: f32) -> Self::Output { Self::seconds_f32(self.as_seconds_f32() * rhs) } } impl Mul for f32 { type Output = Duration; fn mul(self, rhs: Duration) -> Self::Output { rhs * self } } impl Mul for Duration { type Output = Self; fn mul(self, rhs: f64) -> Self::Output { Self::seconds_f64(self.as_seconds_f64() * rhs) } } impl Mul 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 for Duration { type Output = Self; fn div(self, rhs: f32) -> Self::Output { Self::seconds_f32(self.as_seconds_f32() / rhs) } } impl Div 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 for Duration { type Output = f64; fn div(self, rhs: StdDuration) -> Self::Output { self.as_seconds_f64() / rhs.as_secs_f64() } } impl Div for StdDuration { type Output = f64; fn div(self, rhs: Duration) -> Self::Output { self.as_secs_f64() / rhs.as_seconds_f64() } } impl PartialEq for Duration { fn eq(&self, rhs: &StdDuration) -> bool { Ok(*self) == Self::try_from(*rhs) } } impl PartialEq for StdDuration { fn eq(&self, rhs: &Duration) -> bool { rhs == self } } impl PartialOrd for Duration { fn partial_cmp(&self, rhs: &StdDuration) -> Option { if rhs.as_secs() > i64::MAX.cast_unsigned() { return Some(Ordering::Less); } Some( self.seconds .cmp(&rhs.as_secs().cast_signed()) .then_with(|| { self.nanoseconds .get() .cmp(&rhs.subsec_nanos().cast_signed()) }), ) } } impl PartialOrd for StdDuration { fn partial_cmp(&self, rhs: &Duration) -> Option { rhs.partial_cmp(self).map(Ordering::reverse) } } impl Sum for Duration { fn sum>(iter: I) -> Self { iter.reduce(|a, b| a + b).unwrap_or_default() } } impl<'a> Sum<&'a Self> for Duration { fn sum>(iter: I) -> Self { iter.copied().sum() } } // endregion trait impls time-0.3.37/src/error/component_range.rs000064400000000000000000000052531046102023000163250ustar 00000000000000//! 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 for crate::Error { fn from(original: ComponentRange) -> Self { Self::ComponentRange(original) } } impl TryFrom for ComponentRange { type Error = error::DifferentVariant; fn try_from(err: crate::Error) -> Result { 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(self) -> E { E::invalid_value(serde::de::Unexpected::Signed(self.value), &self) } } #[cfg(feature = "std")] #[allow(clippy::std_instead_of_core)] impl std::error::Error for ComponentRange {} time-0.3.37/src/error/conversion_range.rs000064400000000000000000000017271046102023000165120ustar 00000000000000//! 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")] #[allow(clippy::std_instead_of_core)] impl std::error::Error for ConversionRange {} impl From for crate::Error { fn from(err: ConversionRange) -> Self { Self::ConversionRange(err) } } impl TryFrom for ConversionRange { type Error = error::DifferentVariant; fn try_from(err: crate::Error) -> Result { match err { crate::Error::ConversionRange(err) => Ok(err), _ => Err(error::DifferentVariant), } } } time-0.3.37/src/error/different_variant.rs000064400000000000000000000016751046102023000166450ustar 00000000000000//! 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")] #[allow(clippy::std_instead_of_core)] impl std::error::Error for DifferentVariant {} impl From for crate::Error { fn from(err: DifferentVariant) -> Self { Self::DifferentVariant(err) } } impl TryFrom for DifferentVariant { type Error = Self; fn try_from(err: crate::Error) -> Result { match err { crate::Error::DifferentVariant(err) => Ok(err), _ => Err(Self), } } } time-0.3.37/src/error/format.rs000064400000000000000000000051201046102023000144300ustar 00000000000000//! 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 for Format { fn from(err: io::Error) -> Self { Self::StdIo(err) } } impl TryFrom for io::Error { type Error = error::DifferentVariant; fn try_from(err: Format) -> Result { match err { Format::StdIo(err) => Ok(err), _ => Err(error::DifferentVariant), } } } #[cfg(feature = "std")] #[allow(clippy::std_instead_of_core)] 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 for crate::Error { fn from(original: Format) -> Self { Self::Format(original) } } impl TryFrom for Format { type Error = error::DifferentVariant; fn try_from(err: crate::Error) -> Result { 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(self) -> S::Error { use serde::ser::Error; S::Error::custom(self) } } time-0.3.37/src/error/indeterminate_offset.rs000064400000000000000000000017121046102023000173410ustar 00000000000000//! 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")] #[allow(clippy::std_instead_of_core)] impl std::error::Error for IndeterminateOffset {} impl From for crate::Error { fn from(err: IndeterminateOffset) -> Self { Self::IndeterminateOffset(err) } } impl TryFrom for IndeterminateOffset { type Error = error::DifferentVariant; fn try_from(err: crate::Error) -> Result { match err { crate::Error::IndeterminateOffset(err) => Ok(err), _ => Err(error::DifferentVariant), } } } time-0.3.37/src/error/invalid_format_description.rs000064400000000000000000000102151046102023000205420ustar 00000000000000//! Invalid format description use alloc::string::String; use core::fmt; use crate::error; /// The format description provided was not valid. #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq)] pub enum InvalidFormatDescription { /// There was a bracket pair that was opened but not closed. #[non_exhaustive] UnclosedOpeningBracket { /// The zero-based index of the opening bracket. index: usize, }, /// A component name is not valid. #[non_exhaustive] InvalidComponentName { /// The name of the invalid component name. name: String, /// The zero-based index the component name starts at. index: usize, }, /// A modifier is not valid. #[non_exhaustive] InvalidModifier { /// The value of the invalid modifier. value: String, /// The zero-based index the modifier starts at. index: usize, }, /// A component name is missing. #[non_exhaustive] MissingComponentName { /// The zero-based index where the component name should start. index: usize, }, /// A required modifier is missing. #[non_exhaustive] MissingRequiredModifier { /// The name of the modifier that is missing. name: &'static str, /// The zero-based index of the component. index: usize, }, /// Something was expected, but not found. #[non_exhaustive] Expected { /// What was expected to be present, but wasn't. what: &'static str, /// The zero-based index the item was expected to be found at. index: usize, }, /// Certain behavior is not supported in the given context. #[non_exhaustive] NotSupported { /// The behavior that is not supported. what: &'static str, /// The context in which the behavior is not supported. context: &'static str, /// The zero-based index the error occurred at. index: usize, }, } impl From for crate::Error { fn from(original: InvalidFormatDescription) -> Self { Self::InvalidFormatDescription(original) } } impl TryFrom for InvalidFormatDescription { type Error = error::DifferentVariant; fn try_from(err: crate::Error) -> Result { match err { crate::Error::InvalidFormatDescription(err) => Ok(err), _ => Err(error::DifferentVariant), } } } impl fmt::Display for InvalidFormatDescription { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use InvalidFormatDescription::*; match self { UnclosedOpeningBracket { index } => { write!(f, "unclosed opening bracket at byte index {index}") } InvalidComponentName { name, index } => { write!(f, "invalid component name `{name}` at byte index {index}") } InvalidModifier { value, index } => { write!(f, "invalid modifier `{value}` at byte index {index}") } MissingComponentName { index } => { write!(f, "missing component name at byte index {index}") } MissingRequiredModifier { name, index } => { write!( f, "missing required modifier `{name}` for component at byte index {index}" ) } Expected { what: expected, index, } => { write!(f, "expected {expected} at byte index {index}") } NotSupported { what, context, index, } => { if context.is_empty() { write!(f, "{what} is not supported at byte index {index}") } else { write!( f, "{what} is not supported in {context} at byte index {index}" ) } } } } } #[cfg(feature = "std")] #[allow(clippy::std_instead_of_core)] impl std::error::Error for InvalidFormatDescription {} time-0.3.37/src/error/invalid_variant.rs000064400000000000000000000016761046102023000163260ustar 00000000000000//! 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")] #[allow(clippy::std_instead_of_core)] impl std::error::Error for InvalidVariant {} impl From for crate::Error { fn from(err: InvalidVariant) -> Self { Self::InvalidVariant(err) } } impl TryFrom for InvalidVariant { type Error = crate::error::DifferentVariant; fn try_from(err: crate::Error) -> Result { match err { crate::Error::InvalidVariant(err) => Ok(err), _ => Err(crate::error::DifferentVariant), } } } time-0.3.37/src/error/mod.rs000064400000000000000000000113131046102023000137200ustar 00000000000000//! 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; #[cfg(feature = "parsing")] use crate::internal_macros::bug; /// A unified error type for anything returned by a method in the time crate. /// /// This can be used when you either don't know or don't care about the exact error returned. /// `Result<_, time::Error>` (or its alias `time::Result<_>`) will work in these situations. #[allow(missing_copy_implementations, variant_size_differences)] #[non_exhaustive] #[derive(Debug)] pub enum Error { #[allow(missing_docs)] ConversionRange(ConversionRange), #[allow(missing_docs)] ComponentRange(ComponentRange), #[cfg(feature = "local-offset")] #[allow(missing_docs)] IndeterminateOffset(IndeterminateOffset), #[cfg(feature = "formatting")] #[allow(missing_docs)] Format(Format), #[cfg(feature = "parsing")] #[allow(missing_docs)] ParseFromDescription(ParseFromDescription), #[cfg(feature = "parsing")] #[allow(missing_docs)] #[non_exhaustive] #[deprecated( since = "0.3.28", note = "no longer output. moved to the `ParseFromDescription` variant" )] UnexpectedTrailingCharacters, #[cfg(feature = "parsing")] #[allow(missing_docs)] TryFromParsed(TryFromParsed), #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))] #[allow(missing_docs)] InvalidFormatDescription(InvalidFormatDescription), #[allow(missing_docs)] DifferentVariant(DifferentVariant), #[allow(missing_docs)] InvalidVariant(InvalidVariant), } 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")] #[allow(deprecated)] Self::UnexpectedTrailingCharacters => bug!("variant should not be used"), #[cfg(feature = "parsing")] Self::TryFromParsed(e) => e.fmt(f), #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))] Self::InvalidFormatDescription(e) => e.fmt(f), Self::DifferentVariant(e) => e.fmt(f), Self::InvalidVariant(e) => e.fmt(f), } } } #[cfg(feature = "std")] #[allow(clippy::std_instead_of_core)] 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")] #[allow(deprecated)] Self::UnexpectedTrailingCharacters => bug!("variant should not be used"), #[cfg(feature = "parsing")] Self::TryFromParsed(err) => Some(err), #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))] Self::InvalidFormatDescription(err) => Some(err), Self::DifferentVariant(err) => Some(err), Self::InvalidVariant(err) => Some(err), } } } time-0.3.37/src/error/parse.rs000064400000000000000000000063031046102023000142560ustar 00000000000000//! Error that occurred at some stage of parsing use core::fmt; use crate::error::{self, ParseFromDescription, TryFromParsed}; use crate::internal_macros::bug; /// An error that occurred at some stage of parsing. #[allow(variant_size_differences)] #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Parse { #[allow(missing_docs)] TryFromParsed(TryFromParsed), #[allow(missing_docs)] ParseFromDescription(ParseFromDescription), /// The input should have ended, but there were characters remaining. #[non_exhaustive] #[deprecated( since = "0.3.28", note = "no longer output. moved to the `ParseFromDescription` variant" )] UnexpectedTrailingCharacters, } 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), #[allow(deprecated)] Self::UnexpectedTrailingCharacters => bug!("variant should not be used"), } } } #[cfg(feature = "std")] #[allow(clippy::std_instead_of_core)] 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), #[allow(deprecated)] Self::UnexpectedTrailingCharacters => bug!("variant should not be used"), } } } impl From for Parse { fn from(err: TryFromParsed) -> Self { Self::TryFromParsed(err) } } impl TryFrom for TryFromParsed { type Error = error::DifferentVariant; fn try_from(err: Parse) -> Result { match err { Parse::TryFromParsed(err) => Ok(err), _ => Err(error::DifferentVariant), } } } impl From for Parse { fn from(err: ParseFromDescription) -> Self { Self::ParseFromDescription(err) } } impl TryFrom for ParseFromDescription { type Error = error::DifferentVariant; fn try_from(err: Parse) -> Result { match err { Parse::ParseFromDescription(err) => Ok(err), _ => Err(error::DifferentVariant), } } } impl From for crate::Error { fn from(err: Parse) -> Self { match err { Parse::TryFromParsed(err) => Self::TryFromParsed(err), Parse::ParseFromDescription(err) => Self::ParseFromDescription(err), #[allow(deprecated)] Parse::UnexpectedTrailingCharacters => bug!("variant should not be used"), } } } impl TryFrom for Parse { type Error = error::DifferentVariant; fn try_from(err: crate::Error) -> Result { match err { crate::Error::ParseFromDescription(err) => Ok(Self::ParseFromDescription(err)), #[allow(deprecated)] crate::Error::UnexpectedTrailingCharacters => bug!("variant should not be used"), crate::Error::TryFromParsed(err) => Ok(Self::TryFromParsed(err)), _ => Err(error::DifferentVariant), } } } time-0.3.37/src/error/parse_from_description.rs000064400000000000000000000033061046102023000177040ustar 00000000000000//! Error parsing an input into a [`Parsed`](crate::parsing::Parsed) struct use core::fmt; use crate::error; /// An error that occurred while parsing the input into a [`Parsed`](crate::parsing::Parsed) struct. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ParseFromDescription { /// A string literal was not what was expected. #[non_exhaustive] InvalidLiteral, /// A dynamic component was not valid. InvalidComponent(&'static str), /// The input was expected to have ended, but there are characters that remain. #[non_exhaustive] UnexpectedTrailingCharacters, } impl fmt::Display for ParseFromDescription { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::InvalidLiteral => f.write_str("a character literal was not valid"), Self::InvalidComponent(name) => { write!(f, "the '{name}' component could not be parsed") } Self::UnexpectedTrailingCharacters => { f.write_str("unexpected trailing characters; the end of input was expected") } } } } #[cfg(feature = "std")] #[allow(clippy::std_instead_of_core)] impl std::error::Error for ParseFromDescription {} impl From for crate::Error { fn from(original: ParseFromDescription) -> Self { Self::ParseFromDescription(original) } } impl TryFrom for ParseFromDescription { type Error = error::DifferentVariant; fn try_from(err: crate::Error) -> Result { match err { crate::Error::ParseFromDescription(err) => Ok(err), _ => Err(error::DifferentVariant), } } } time-0.3.37/src/error/try_from_parsed.rs000064400000000000000000000041131046102023000163400ustar 00000000000000//! 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 for TryFromParsed { fn from(v: error::ComponentRange) -> Self { Self::ComponentRange(v) } } impl TryFrom for error::ComponentRange { type Error = error::DifferentVariant; fn try_from(err: TryFromParsed) -> Result { match err { TryFromParsed::ComponentRange(err) => Ok(err), _ => Err(error::DifferentVariant), } } } #[cfg(feature = "std")] #[allow(clippy::std_instead_of_core)] 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 for crate::Error { fn from(original: TryFromParsed) -> Self { Self::TryFromParsed(original) } } impl TryFrom for TryFromParsed { type Error = error::DifferentVariant; fn try_from(err: crate::Error) -> Result { match err { crate::Error::TryFromParsed(err) => Ok(err), _ => Err(error::DifferentVariant), } } } time-0.3.37/src/ext/digit_count.rs000064400000000000000000000014251046102023000151230ustar 00000000000000use num_conv::prelude::*; /// A trait that indicates the formatted width of the value can be determined. /// /// Note that this should not be implemented for any signed integers. This forces the caller to /// write the sign if desired. pub(crate) trait DigitCount { /// The number of digits in the stringified value. fn num_digits(self) -> u8; } /// A macro to generate implementations of `DigitCount` for unsigned integers. macro_rules! impl_digit_count { ($($t:ty),* $(,)?) => { $(impl DigitCount for $t { fn num_digits(self) -> u8 { match self.checked_ilog10() { Some(n) => n.truncate::() + 1, None => 1, } } })* }; } impl_digit_count!(u8, u16, u32); time-0.3.37/src/ext/instant.rs000064400000000000000000000072361046102023000143010ustar 00000000000000use std::time::Instant as StdInstant; use crate::Duration; /// Sealed trait to prevent downstream implementations. mod sealed { /// A trait that cannot be implemented by downstream users. pub trait Sealed: Sized {} impl Sealed for std::time::Instant {} } /// An extension trait for [`std::time::Instant`] that adds methods for /// [`time::Duration`](Duration)s. pub trait InstantExt: sealed::Sealed { /// # Panics /// /// This function may panic if the resulting point in time cannot be represented by the /// underlying data structure. See [`InstantExt::checked_add_signed`] for a non-panicking /// version. fn add_signed(self, duration: Duration) -> Self { self.checked_add_signed(duration) .expect("overflow when adding duration to instant") } /// # Panics /// /// This function may panic if the resulting point in time cannot be represented by the /// underlying data structure. See [`InstantExt::checked_sub_signed`] for a non-panicking /// version. fn sub_signed(self, duration: Duration) -> Self { self.checked_sub_signed(duration) .expect("overflow when subtracting duration from instant") } /// Returns `Some(t)` where `t` is the time `self.checked_add_signed(duration)` if `t` can be /// represented as `Instant` (which means it's inside the bounds of the underlying data /// structure), `None` otherwise. fn checked_add_signed(&self, duration: Duration) -> Option; /// Returns `Some(t)` where `t` is the time `self.checked_sub_signed(duration)` if `t` can be /// represented as `Instant` (which means it's inside the bounds of the underlying data /// structure), `None` otherwise. fn checked_sub_signed(&self, duration: Duration) -> Option; /// Returns the amount of time elapsed from another instant to this one. This will be negative /// if `earlier` is later than `self`. /// /// # Example /// /// ```rust /// # use std::thread::sleep; /// # use std::time::{Duration, Instant}; /// # use time::ext::InstantExt; /// let now = Instant::now(); /// sleep(Duration::new(1, 0)); /// let new_now = Instant::now(); /// println!("{:?}", new_now.signed_duration_since(now)); // positive /// println!("{:?}", now.signed_duration_since(new_now)); // negative /// ``` fn signed_duration_since(&self, earlier: Self) -> Duration; } impl InstantExt for StdInstant { fn checked_add_signed(&self, duration: Duration) -> Option { if duration.is_positive() { self.checked_add(duration.unsigned_abs()) } else if duration.is_negative() { #[allow(clippy::unchecked_duration_subtraction)] self.checked_sub(duration.unsigned_abs()) } else { debug_assert!(duration.is_zero()); Some(*self) } } fn checked_sub_signed(&self, duration: Duration) -> Option { if duration.is_positive() { #[allow(clippy::unchecked_duration_subtraction)] self.checked_sub(duration.unsigned_abs()) } else if duration.is_negative() { self.checked_add(duration.unsigned_abs()) } else { debug_assert!(duration.is_zero()); Some(*self) } } fn signed_duration_since(&self, earlier: Self) -> Duration { if *self > earlier { self.saturating_duration_since(earlier) .try_into() .unwrap_or(Duration::MAX) } else { earlier .saturating_duration_since(*self) .try_into() .map_or(Duration::MIN, |d: Duration| -d) } } } time-0.3.37/src/ext/mod.rs000064400000000000000000000005341046102023000133720ustar 00000000000000//! Extension traits. mod digit_count; #[cfg(feature = "std")] mod instant; mod numerical_duration; mod numerical_std_duration; pub(crate) use self::digit_count::DigitCount; #[cfg(feature = "std")] pub use self::instant::InstantExt; pub use self::numerical_duration::NumericalDuration; pub use self::numerical_std_duration::NumericalStdDuration; time-0.3.37/src/ext/numerical_duration.rs000064400000000000000000000103531046102023000164770ustar 00000000000000use crate::convert::*; use crate::Duration; /// Sealed trait to prevent downstream implementations. mod sealed { /// A trait that cannot be implemented by downstream users. pub trait Sealed {} impl Sealed for i64 {} impl Sealed for f64 {} } /// Create [`Duration`]s from numeric literals. /// /// # Examples /// /// Basic construction of [`Duration`]s. /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!(5.nanoseconds(), Duration::nanoseconds(5)); /// assert_eq!(5.microseconds(), Duration::microseconds(5)); /// assert_eq!(5.milliseconds(), Duration::milliseconds(5)); /// assert_eq!(5.seconds(), Duration::seconds(5)); /// assert_eq!(5.minutes(), Duration::minutes(5)); /// assert_eq!(5.hours(), Duration::hours(5)); /// assert_eq!(5.days(), Duration::days(5)); /// assert_eq!(5.weeks(), Duration::weeks(5)); /// ``` /// /// Signed integers work as well! /// /// ```rust /// # use time::{Duration, ext::NumericalDuration}; /// assert_eq!((-5).nanoseconds(), Duration::nanoseconds(-5)); /// assert_eq!((-5).microseconds(), Duration::microseconds(-5)); /// assert_eq!((-5).milliseconds(), Duration::milliseconds(-5)); /// assert_eq!((-5).seconds(), Duration::seconds(-5)); /// assert_eq!((-5).minutes(), Duration::minutes(-5)); /// assert_eq!((-5).hours(), Duration::hours(-5)); /// assert_eq!((-5).days(), Duration::days(-5)); /// assert_eq!((-5).weeks(), Duration::weeks(-5)); /// ``` /// /// Just like any other [`Duration`], they can be added, subtracted, etc. /// /// ```rust /// # use time::ext::NumericalDuration; /// assert_eq!(2.seconds() + 500.milliseconds(), 2_500.milliseconds()); /// assert_eq!(2.seconds() - 500.milliseconds(), 1_500.milliseconds()); /// ``` /// /// When called on floating point values, any remainder of the floating point value will be /// truncated. Keep in mind that floating point numbers are inherently imprecise and have /// limited capacity. pub trait NumericalDuration: sealed::Sealed { /// Create a [`Duration`] from the number of nanoseconds. fn nanoseconds(self) -> Duration; /// Create a [`Duration`] from the number of microseconds. fn microseconds(self) -> Duration; /// Create a [`Duration`] from the number of milliseconds. fn milliseconds(self) -> Duration; /// Create a [`Duration`] from the number of seconds. fn seconds(self) -> Duration; /// Create a [`Duration`] from the number of minutes. fn minutes(self) -> Duration; /// Create a [`Duration`] from the number of hours. fn hours(self) -> Duration; /// Create a [`Duration`] from the number of days. fn days(self) -> Duration; /// Create a [`Duration`] from the number of weeks. fn weeks(self) -> Duration; } impl NumericalDuration for i64 { fn nanoseconds(self) -> Duration { Duration::nanoseconds(self) } fn microseconds(self) -> Duration { Duration::microseconds(self) } fn milliseconds(self) -> Duration { Duration::milliseconds(self) } fn seconds(self) -> Duration { Duration::seconds(self) } fn minutes(self) -> Duration { Duration::minutes(self) } fn hours(self) -> Duration { Duration::hours(self) } fn days(self) -> Duration { Duration::days(self) } fn weeks(self) -> Duration { Duration::weeks(self) } } impl NumericalDuration for f64 { fn nanoseconds(self) -> Duration { Duration::nanoseconds(self as _) } fn microseconds(self) -> Duration { Duration::nanoseconds((self * Nanosecond::per(Microsecond) as Self) as _) } fn milliseconds(self) -> Duration { Duration::nanoseconds((self * Nanosecond::per(Millisecond) as Self) as _) } fn seconds(self) -> Duration { Duration::nanoseconds((self * Nanosecond::per(Second) as Self) as _) } fn minutes(self) -> Duration { Duration::nanoseconds((self * Nanosecond::per(Minute) as Self) as _) } fn hours(self) -> Duration { Duration::nanoseconds((self * Nanosecond::per(Hour) as Self) as _) } fn days(self) -> Duration { Duration::nanoseconds((self * Nanosecond::per(Day) as Self) as _) } fn weeks(self) -> Duration { Duration::nanoseconds((self * Nanosecond::per(Week) as Self) as _) } } time-0.3.37/src/ext/numerical_std_duration.rs000064400000000000000000000134501046102023000173520ustar 00000000000000use core::time::Duration as StdDuration; use num_conv::prelude::*; use crate::convert::*; /// Sealed trait to prevent downstream implementations. mod sealed { /// A trait that cannot be implemented by downstream users. pub trait Sealed {} impl Sealed for u64 {} impl Sealed for f64 {} } /// Create [`std::time::Duration`]s from numeric literals. /// /// # Examples /// /// Basic construction of [`std::time::Duration`]s. /// /// ```rust /// # use time::ext::NumericalStdDuration; /// # use core::time::Duration; /// assert_eq!(5.std_nanoseconds(), Duration::from_nanos(5)); /// assert_eq!(5.std_microseconds(), Duration::from_micros(5)); /// assert_eq!(5.std_milliseconds(), Duration::from_millis(5)); /// assert_eq!(5.std_seconds(), Duration::from_secs(5)); /// assert_eq!(5.std_minutes(), Duration::from_secs(5 * 60)); /// assert_eq!(5.std_hours(), Duration::from_secs(5 * 3_600)); /// assert_eq!(5.std_days(), Duration::from_secs(5 * 86_400)); /// assert_eq!(5.std_weeks(), Duration::from_secs(5 * 604_800)); /// ``` /// /// Just like any other [`std::time::Duration`], they can be added, subtracted, etc. /// /// ```rust /// # use time::ext::NumericalStdDuration; /// assert_eq!( /// 2.std_seconds() + 500.std_milliseconds(), /// 2_500.std_milliseconds() /// ); /// assert_eq!( /// 2.std_seconds() - 500.std_milliseconds(), /// 1_500.std_milliseconds() /// ); /// ``` /// /// When called on floating point values, any remainder of the floating point value will be /// truncated. Keep in mind that floating point numbers are inherently imprecise and have /// limited capacity. pub trait NumericalStdDuration: sealed::Sealed { /// Create a [`std::time::Duration`] from the number of nanoseconds. fn std_nanoseconds(self) -> StdDuration; /// Create a [`std::time::Duration`] from the number of microseconds. fn std_microseconds(self) -> StdDuration; /// Create a [`std::time::Duration`] from the number of milliseconds. fn std_milliseconds(self) -> StdDuration; /// Create a [`std::time::Duration`] from the number of seconds. fn std_seconds(self) -> StdDuration; /// Create a [`std::time::Duration`] from the number of minutes. fn std_minutes(self) -> StdDuration; /// Create a [`std::time::Duration`] from the number of hours. fn std_hours(self) -> StdDuration; /// Create a [`std::time::Duration`] from the number of days. fn std_days(self) -> StdDuration; /// Create a [`std::time::Duration`] from the number of weeks. fn std_weeks(self) -> StdDuration; } impl NumericalStdDuration for u64 { fn std_nanoseconds(self) -> StdDuration { StdDuration::from_nanos(self) } fn std_microseconds(self) -> StdDuration { StdDuration::from_micros(self) } fn std_milliseconds(self) -> StdDuration { StdDuration::from_millis(self) } fn std_seconds(self) -> StdDuration { StdDuration::from_secs(self) } /// # Panics /// /// This may panic if an overflow occurs. fn std_minutes(self) -> StdDuration { StdDuration::from_secs( self.checked_mul(Second::per(Minute).extend()) .expect("overflow constructing `time::Duration`"), ) } /// # Panics /// /// This may panic if an overflow occurs. fn std_hours(self) -> StdDuration { StdDuration::from_secs( self.checked_mul(Second::per(Hour).extend()) .expect("overflow constructing `time::Duration`"), ) } /// # Panics /// /// This may panic if an overflow occurs. fn std_days(self) -> StdDuration { StdDuration::from_secs( self.checked_mul(Second::per(Day).extend()) .expect("overflow constructing `time::Duration`"), ) } /// # Panics /// /// This may panic if an overflow occurs. fn std_weeks(self) -> StdDuration { StdDuration::from_secs( self.checked_mul(Second::per(Week).extend()) .expect("overflow constructing `time::Duration`"), ) } } impl NumericalStdDuration for f64 { /// # Panics /// /// This will panic if self is negative. fn std_nanoseconds(self) -> StdDuration { assert!(self >= 0.); StdDuration::from_nanos(self as _) } /// # Panics /// /// This will panic if self is negative. fn std_microseconds(self) -> StdDuration { assert!(self >= 0.); StdDuration::from_nanos((self * Nanosecond::per(Microsecond) as Self) as _) } /// # Panics /// /// This will panic if self is negative. fn std_milliseconds(self) -> StdDuration { assert!(self >= 0.); StdDuration::from_nanos((self * Nanosecond::per(Millisecond) as Self) as _) } /// # Panics /// /// This will panic if self is negative. fn std_seconds(self) -> StdDuration { assert!(self >= 0.); StdDuration::from_nanos((self * Nanosecond::per(Second) as Self) as _) } /// # Panics /// /// This will panic if self is negative. fn std_minutes(self) -> StdDuration { assert!(self >= 0.); StdDuration::from_nanos((self * Nanosecond::per(Minute) as Self) as _) } /// # Panics /// /// This will panic if self is negative. fn std_hours(self) -> StdDuration { assert!(self >= 0.); StdDuration::from_nanos((self * Nanosecond::per(Hour) as Self) as _) } /// # Panics /// /// This will panic if self is negative. fn std_days(self) -> StdDuration { assert!(self >= 0.); StdDuration::from_nanos((self * Nanosecond::per(Day) as Self) as _) } /// # Panics /// /// This will panic if self is negative. fn std_weeks(self) -> StdDuration { assert!(self >= 0.); StdDuration::from_nanos((self * Nanosecond::per(Week) as Self) as _) } } time-0.3.37/src/format_description/borrowed_format_item.rs000064400000000000000000000067151046102023000221260ustar 00000000000000//! 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**: These bytes **should** be UTF-8, but are not required to be. The value is passed /// through `String::from_utf8_lossy` when necessary. Literal(&'a [u8]), /// A minimal representation of a single non-literal item. Component(Component), /// 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 for BorrowedFormatItem<'_> { fn from(component: Component) -> Self { Self::Component(component) } } impl TryFrom> for Component { type Error = error::DifferentVariant; fn try_from(value: BorrowedFormatItem<'_>) -> Result { 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> for &[BorrowedFormatItem<'a>] { type Error = error::DifferentVariant; fn try_from(value: BorrowedFormatItem<'a>) -> Result { match value { BorrowedFormatItem::Compound(items) => Ok(items), _ => Err(error::DifferentVariant), } } } impl PartialEq for BorrowedFormatItem<'_> { fn eq(&self, rhs: &Component) -> bool { matches!(self, Self::Component(component) if component == rhs) } } impl PartialEq> 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> for &[BorrowedFormatItem<'_>] { fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool { rhs == self } } time-0.3.37/src/format_description/component.rs000064400000000000000000000027511046102023000177130ustar 00000000000000//! Part of a format description. use crate::format_description::modifier; /// A component of a larger format description. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Component { /// Day of the month. Day(modifier::Day), /// Month of the year. Month(modifier::Month), /// Ordinal day of the year. Ordinal(modifier::Ordinal), /// Day of the week. Weekday(modifier::Weekday), /// Week within the year. WeekNumber(modifier::WeekNumber), /// Year of the date. Year(modifier::Year), /// Hour of the day. Hour(modifier::Hour), /// Minute within the hour. Minute(modifier::Minute), /// AM/PM part of the time. Period(modifier::Period), /// Second within the minute. Second(modifier::Second), /// Subsecond within the second. Subsecond(modifier::Subsecond), /// Hour of the UTC offset. OffsetHour(modifier::OffsetHour), /// Minute within the hour of the UTC offset. OffsetMinute(modifier::OffsetMinute), /// Second within the minute of the UTC offset. OffsetSecond(modifier::OffsetSecond), /// A number of bytes to ignore when parsing. This has no effect on formatting. Ignore(modifier::Ignore), /// A Unix timestamp. UnixTimestamp(modifier::UnixTimestamp), /// The end of input. Parsing this component will fail if there is any input remaining. This /// component neither affects formatting nor consumes any input when parsing. End(modifier::End), } time-0.3.37/src/format_description/mod.rs000064400000000000000000000024171046102023000164670ustar 00000000000000//! Description of how types should be formatted and parsed. //! //! The formatted value will be output to the provided writer. Format descriptions can be //! [well-known](crate::format_description::well_known) or obtained by using the //! [`format_description!`](crate::macros::format_description) macro or a function listed below. //! //! For examples, see the implementors of [Formattable](crate::formatting::Formattable), //! e.g. [`well_known::Rfc3339`]. mod borrowed_format_item; mod component; pub mod modifier; #[cfg(feature = "alloc")] mod owned_format_item; #[cfg(feature = "alloc")] mod parse; pub use borrowed_format_item::BorrowedFormatItem; #[doc(hidden)] #[deprecated(since = "0.3.37", note = "use `BorrowedFormatItem` for clarity")] pub use borrowed_format_item::BorrowedFormatItem as FormatItem; #[cfg(feature = "alloc")] pub use owned_format_item::OwnedFormatItem; pub use self::component::Component; #[cfg(feature = "alloc")] pub use self::parse::{ parse, parse_borrowed, parse_owned, parse_strftime_borrowed, parse_strftime_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; } time-0.3.37/src/format_description/modifier.rs000064400000000000000000000334021046102023000175040ustar 00000000000000//! Various modifiers for components. use core::num::NonZeroU16; // region: date modifiers /// Day of the month. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Day { /// The padding to obtain the minimum width. pub padding: Padding, } /// The representation of a month. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MonthRepr { /// The number of the month (January is 1, December is 12). Numerical, /// The long form of the month name (e.g. "January"). Long, /// The short form of the month name (e.g. "Jan"). Short, } /// Month of the year. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Month { /// The padding to obtain the minimum width. pub padding: Padding, /// What form of representation should be used? pub repr: MonthRepr, /// Is the value case sensitive when parsing? pub case_sensitive: bool, } /// Ordinal day of the year. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Ordinal { /// The padding to obtain the minimum width. pub padding: Padding, } /// The representation used for the day of the week. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WeekdayRepr { /// The short form of the weekday (e.g. "Mon"). Short, /// The long form of the weekday (e.g. "Monday"). Long, /// A numerical representation using Sunday as the first day of the week. /// /// Sunday is either 0 or 1, depending on the other modifier's value. Sunday, /// A numerical representation using Monday as the first day of the week. /// /// Monday is either 0 or 1, depending on the other modifier's value. Monday, } /// Day of the week. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Weekday { /// What form of representation should be used? pub repr: WeekdayRepr, /// When using a numerical representation, should it be zero or one-indexed? pub one_indexed: bool, /// Is the value case sensitive when parsing? pub case_sensitive: bool, } /// The representation used for the week number. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WeekNumberRepr { /// Week 1 is the week that contains January 4. Iso, /// Week 1 begins on the first Sunday of the calendar year. Sunday, /// Week 1 begins on the first Monday of the calendar year. Monday, } /// Week within the year. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct WeekNumber { /// The padding to obtain the minimum width. pub padding: Padding, /// What kind of representation should be used? pub repr: WeekNumberRepr, } /// The representation used for a year value. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum YearRepr { /// The full value of the year. Full, /// All digits except the last two. Includes the sign, if any. Century, /// Only the last two digits of the year. LastTwo, } /// Year of the date. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Year { /// The padding to obtain the minimum width. pub padding: Padding, /// What kind of representation should be used? pub repr: YearRepr, /// Whether the value is based on the ISO week number or the Gregorian calendar. pub iso_week_based: bool, /// Whether the `+` sign is present when a positive year contains fewer than five digits. pub sign_is_mandatory: bool, } // endregion date modifiers // region: time modifiers /// Hour of the day. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Hour { /// The padding to obtain the minimum width. pub padding: Padding, /// Is the hour displayed using a 12 or 24-hour clock? pub is_12_hour_clock: bool, } /// Minute within the hour. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Minute { /// The padding to obtain the minimum width. pub padding: Padding, } /// AM/PM part of the time. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Period { /// Is the period uppercase or lowercase? pub is_uppercase: bool, /// Is the value case sensitive when parsing? /// /// Note that when `false`, the `is_uppercase` field has no effect on parsing behavior. pub case_sensitive: bool, } /// Second within the minute. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Second { /// The padding to obtain the minimum width. pub padding: Padding, } /// The number of digits present in a subsecond representation. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SubsecondDigits { /// Exactly one digit. One, /// Exactly two digits. Two, /// Exactly three digits. Three, /// Exactly four digits. Four, /// Exactly five digits. Five, /// Exactly six digits. Six, /// Exactly seven digits. Seven, /// Exactly eight digits. Eight, /// Exactly nine digits. Nine, /// Any number of digits (up to nine) that is at least one. When formatting, the minimum digits /// necessary will be used. OneOrMore, } /// Subsecond within the second. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Subsecond { /// How many digits are present in the component? pub digits: SubsecondDigits, } // endregion time modifiers // region: offset modifiers /// Hour of the UTC offset. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OffsetHour { /// Whether the `+` sign is present on positive values. pub sign_is_mandatory: bool, /// The padding to obtain the minimum width. pub padding: Padding, } /// Minute within the hour of the UTC offset. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OffsetMinute { /// The padding to obtain the minimum width. pub padding: Padding, } /// Second within the minute of the UTC offset. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OffsetSecond { /// The padding to obtain the minimum width. pub padding: Padding, } // endregion offset modifiers /// Type of padding to ensure a minimum width. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Padding { /// A space character (` `) should be used as padding. Space, /// A zero character (`0`) should be used as padding. Zero, /// There is no padding. This can result in a width below the otherwise minimum number of /// characters. None, } /// Ignore some number of bytes. /// /// This has no effect when formatting. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Ignore { /// The number of bytes to ignore. pub count: NonZeroU16, } // Needed as `Default` is deliberately not implemented for `Ignore`. The number of bytes to ignore // must be explicitly provided. impl Ignore { /// Create an instance of `Ignore` with the provided number of bytes to ignore. pub const fn count(count: NonZeroU16) -> Self { Self { count } } } /// The precision of a Unix timestamp. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UnixTimestampPrecision { /// Seconds since the Unix epoch. Second, /// Milliseconds since the Unix epoch. Millisecond, /// Microseconds since the Unix epoch. Microsecond, /// Nanoseconds since the Unix epoch. Nanosecond, } /// A Unix timestamp. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct UnixTimestamp { /// The precision of the timestamp. pub precision: UnixTimestampPrecision, /// Whether the `+` sign must be present for a non-negative timestamp. pub sign_is_mandatory: bool, } /// The end of input. /// /// There is currently not customization for this modifier. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct End; /// Generate the provided code if and only if `pub` is present. macro_rules! if_pub { (pub $(#[$attr:meta])*; $($x:tt)*) => { $(#[$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 only uses a sign for negative values and is /// [padded with zeroes](Padding::Zero). @pub OffsetHour => Self { sign_is_mandatory: false, padding: Padding::Zero, }; /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). @pub OffsetMinute => Self { padding: Padding::Zero }; /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). @pub OffsetSecond => Self { padding: Padding::Zero }; /// Creates a modifier that indicates the value is [padded with zeroes](Self::Zero). Padding => Self::Zero; /// Creates a modifier that indicates the value represents the [number of seconds](Self::Second) /// since the Unix epoch. UnixTimestampPrecision => Self::Second; /// Creates a modifier that indicates the value represents the [number of /// seconds](UnixTimestampPrecision::Second) since the Unix epoch. The sign is not mandatory. @pub UnixTimestamp => Self { precision: UnixTimestampPrecision::Second, sign_is_mandatory: false, }; /// Creates a modifier used to represent the end of input. @pub End => End; } time-0.3.37/src/format_description/owned_format_item.rs000064400000000000000000000121261046102023000214100ustar 00000000000000//! A format item with owned data. use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; use core::fmt; use crate::error; use crate::format_description::{BorrowedFormatItem, Component}; /// A complete description of how to format and parse a type. #[non_exhaustive] #[derive(Clone, PartialEq, Eq)] pub enum OwnedFormatItem { /// Bytes that are formatted as-is. /// /// **Note**: These bytes **should** be UTF-8, but are not required to be. The value is passed /// through `String::from_utf8_lossy` when necessary. 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), /// 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> for OwnedFormatItem { fn from(item: BorrowedFormatItem<'_>) -> Self { (&item).into() } } impl From<&BorrowedFormatItem<'_>> for OwnedFormatItem { fn from(item: &BorrowedFormatItem<'_>) -> Self { match item { BorrowedFormatItem::Literal(literal) => { Self::Literal(literal.to_vec().into_boxed_slice()) } BorrowedFormatItem::Component(component) => Self::Component(*component), BorrowedFormatItem::Compound(compound) => Self::Compound( compound .iter() .cloned() .map(Into::into) .collect::>() .into_boxed_slice(), ), BorrowedFormatItem::Optional(item) => Self::Optional(Box::new((*item).into())), BorrowedFormatItem::First(items) => Self::First( items .iter() .cloned() .map(Into::into) .collect::>() .into_boxed_slice(), ), } } } impl From>> for OwnedFormatItem { fn from(items: Vec>) -> Self { items.as_slice().into() } } impl<'a, T: AsRef<[BorrowedFormatItem<'a>]> + ?Sized> From<&T> for OwnedFormatItem { fn from(items: &T) -> Self { Self::Compound( items .as_ref() .iter() .cloned() .map(Into::into) .collect::>() .into_boxed_slice(), ) } } // endregion conversions from FormatItem // region: from variants impl From for OwnedFormatItem { fn from(component: Component) -> Self { Self::Component(component) } } impl TryFrom for Component { type Error = error::DifferentVariant; fn try_from(value: OwnedFormatItem) -> Result { match value { OwnedFormatItem::Component(component) => Ok(component), _ => Err(error::DifferentVariant), } } } impl From> for OwnedFormatItem { fn from(items: Vec) -> Self { Self::Compound(items.into_boxed_slice()) } } impl TryFrom for Vec { type Error = error::DifferentVariant; fn try_from(value: OwnedFormatItem) -> Result { match value { OwnedFormatItem::Compound(items) => Ok(items.into_vec()), _ => Err(error::DifferentVariant), } } } // endregion from variants // region: equality impl PartialEq for OwnedFormatItem { fn eq(&self, rhs: &Component) -> bool { matches!(self, Self::Component(component) if component == rhs) } } impl PartialEq 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 for &[OwnedFormatItem] { fn eq(&self, rhs: &OwnedFormatItem) -> bool { rhs == self } } // endregion equality time-0.3.37/src/format_description/parse/ast.rs000064400000000000000000000351041046102023000176100ustar 00000000000000//! AST for parsing format descriptions. use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; use core::iter; use super::{lexer, unused, Error, Location, Spanned, SpannedValue, Unused}; use crate::internal_macros::bug; /// One part of a complete format description. pub(super) enum Item<'a> { /// A literal string, formatted and parsed as-is. /// /// This should never be present inside a nested format description. Literal(Spanned<&'a [u8]>), /// A sequence of brackets. The first acts as the escape character. /// /// This should never be present if the lexer has `BACKSLASH_ESCAPE` set to `true`. EscapedBracket { /// The first bracket. _first: Unused, /// The second bracket. _second: Unused, }, /// Part of a type, along with its modifiers. Component { /// Where the opening bracket was in the format string. _opening_bracket: Unused, /// Whitespace between the opening bracket and name. _leading_whitespace: Unused>>, /// The name of the component. name: Spanned<&'a [u8]>, /// The modifiers for the component. modifiers: Box<[Modifier<'a>]>, /// Whitespace between the modifiers and closing bracket. _trailing_whitespace: Unused>>, /// Where the closing bracket was in the format string. _closing_bracket: Unused, }, /// An optional sequence of items. Optional { /// Where the opening bracket was in the format string. opening_bracket: Location, /// Whitespace between the opening bracket and "optional". _leading_whitespace: Unused>>, /// The "optional" keyword. _optional_kw: Unused>, /// Whitespace between the "optional" keyword and the opening bracket. _whitespace: Unused>, /// The items within the optional sequence. nested_format_description: NestedFormatDescription<'a>, /// Where the closing bracket was in the format string. closing_bracket: Location, }, /// The first matching parse of a sequence of items. First { /// Where the opening bracket was in the format string. opening_bracket: Location, /// Whitespace between the opening bracket and "first". _leading_whitespace: Unused>>, /// The "first" keyword. _first_kw: Unused>, /// Whitespace between the "first" keyword and the opening bracket. _whitespace: Unused>, /// The sequences of items to try. nested_format_descriptions: Box<[NestedFormatDescription<'a>]>, /// Where the closing bracket was in the format string. closing_bracket: Location, }, } /// A format description that is nested within another format description. pub(super) struct NestedFormatDescription<'a> { /// Where the opening bracket was in the format string. pub(super) _opening_bracket: Unused, /// The items within the nested format description. pub(super) items: Box<[Item<'a>]>, /// Where the closing bracket was in the format string. pub(super) _closing_bracket: Unused, /// Whitespace between the closing bracket and the next item. pub(super) _trailing_whitespace: Unused>>, } /// A modifier for a component. pub(super) struct Modifier<'a> { /// Whitespace preceding the modifier. pub(super) _leading_whitespace: Unused>, /// The key of the modifier. pub(super) key: Spanned<&'a [u8]>, /// Where the colon of the modifier was in the format string. pub(super) _colon: Unused, /// The value of the modifier. pub(super) value: Spanned<&'a [u8]>, } /// Parse the provided tokens into an AST. pub(super) fn parse< 'item: 'iter, 'iter, I: Iterator, Error>>, const VERSION: usize, >( tokens: &'iter mut lexer::Lexed, ) -> impl Iterator, Error>> + 'iter { validate_version!(VERSION); parse_inner::<_, false, VERSION>(tokens) } /// Parse the provided tokens into an AST. The const generic indicates whether the resulting /// [`Item`] will be used directly or as part of a [`NestedFormatDescription`]. fn parse_inner< 'item, I: Iterator, Error>>, const NESTED: bool, const VERSION: usize, >( tokens: &mut lexer::Lexed, ) -> impl Iterator, Error>> + '_ { validate_version!(VERSION); iter::from_fn(move || { if NESTED && tokens.peek_closing_bracket().is_some() { return None; } let next = match tokens.next()? { Ok(token) => token, Err(err) => return Some(Err(err)), }; Some(match next { lexer::Token::Literal(Spanned { value: _, span: _ }) if NESTED => { bug!("literal should not be present in nested description") } lexer::Token::Literal(value) => Ok(Item::Literal(value)), lexer::Token::Bracket { kind: lexer::BracketKind::Opening, location, } => { if version!(..=1) { if let Some(second_location) = tokens.next_if_opening_bracket() { Ok(Item::EscapedBracket { _first: unused(location), _second: unused(second_location), }) } else { parse_component::<_, VERSION>(location, tokens) } } else { parse_component::<_, VERSION>(location, tokens) } } lexer::Token::Bracket { kind: lexer::BracketKind::Closing, location: _, } if NESTED => { bug!("closing bracket should be caught by the `if` statement") } lexer::Token::Bracket { kind: lexer::BracketKind::Closing, location: _, } => { bug!("closing bracket should have been consumed by `parse_component`") } lexer::Token::ComponentPart { kind: _, // whitespace is significant in nested components value, } if NESTED => Ok(Item::Literal(value)), lexer::Token::ComponentPart { kind: _, value: _ } => { bug!("component part should have been consumed by `parse_component`") } }) }) } /// Parse a component. This assumes that the opening bracket has already been consumed. fn parse_component< 'a, I: Iterator, Error>>, const VERSION: usize, >( opening_bracket: Location, tokens: &mut lexer::Lexed, ) -> Result, Error> { validate_version!(VERSION); let leading_whitespace = tokens.next_if_whitespace(); let Some(name) = tokens.next_if_not_whitespace() else { let span = match leading_whitespace { Some(Spanned { value: _, span }) => span, None => opening_bracket.to_self(), }; return Err(Error { _inner: unused(span.error("expected component name")), public: crate::error::InvalidFormatDescription::MissingComponentName { index: span.start.byte as _, }, }); }; if *name == b"optional" { let Some(whitespace) = tokens.next_if_whitespace() else { return Err(Error { _inner: unused(name.span.error("expected whitespace after `optional`")), public: crate::error::InvalidFormatDescription::Expected { what: "whitespace after `optional`", index: name.span.end.byte as _, }, }); }; let nested = parse_nested::<_, VERSION>(whitespace.span.end, tokens)?; let Some(closing_bracket) = tokens.next_if_closing_bracket() else { return Err(Error { _inner: unused(opening_bracket.error("unclosed bracket")), public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { index: opening_bracket.byte as _, }, }); }; return Ok(Item::Optional { opening_bracket, _leading_whitespace: unused(leading_whitespace), _optional_kw: unused(name), _whitespace: unused(whitespace), nested_format_description: nested, closing_bracket, }); } if *name == b"first" { let Some(whitespace) = tokens.next_if_whitespace() else { return Err(Error { _inner: unused(name.span.error("expected whitespace after `first`")), public: crate::error::InvalidFormatDescription::Expected { what: "whitespace after `first`", index: name.span.end.byte as _, }, }); }; let mut nested_format_descriptions = Vec::new(); while let Ok(description) = parse_nested::<_, VERSION>(whitespace.span.end, tokens) { nested_format_descriptions.push(description); } let Some(closing_bracket) = tokens.next_if_closing_bracket() else { return Err(Error { _inner: unused(opening_bracket.error("unclosed bracket")), public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { index: opening_bracket.byte as _, }, }); }; return Ok(Item::First { opening_bracket, _leading_whitespace: unused(leading_whitespace), _first_kw: unused(name), _whitespace: unused(whitespace), nested_format_descriptions: nested_format_descriptions.into_boxed_slice(), closing_bracket, }); } let mut modifiers = Vec::new(); let trailing_whitespace = loop { let Some(whitespace) = tokens.next_if_whitespace() else { break None; }; // This is not necessary for proper parsing, but provides a much better error when a nested // description is used where it's not allowed. if let Some(location) = tokens.next_if_opening_bracket() { return Err(Error { _inner: unused( location .to_self() .error("modifier must be of the form `key:value`"), ), public: crate::error::InvalidFormatDescription::InvalidModifier { value: String::from("["), index: location.byte as _, }, }); } let Some(Spanned { value, span }) = tokens.next_if_not_whitespace() else { break Some(whitespace); }; let Some(colon_index) = value.iter().position(|&b| b == b':') else { return Err(Error { _inner: unused(span.error("modifier must be of the form `key:value`")), public: crate::error::InvalidFormatDescription::InvalidModifier { value: String::from_utf8_lossy(value).into_owned(), index: span.start.byte as _, }, }); }; let key = &value[..colon_index]; let value = &value[colon_index + 1..]; if key.is_empty() { return Err(Error { _inner: unused(span.shrink_to_start().error("expected modifier key")), public: crate::error::InvalidFormatDescription::InvalidModifier { value: String::new(), index: span.start.byte as _, }, }); } if value.is_empty() { return Err(Error { _inner: unused(span.shrink_to_end().error("expected modifier value")), public: crate::error::InvalidFormatDescription::InvalidModifier { value: String::new(), index: span.shrink_to_end().start.byte as _, }, }); } modifiers.push(Modifier { _leading_whitespace: unused(whitespace), key: key.spanned(span.shrink_to_before(colon_index as _)), _colon: unused(span.start.offset(colon_index as _)), value: value.spanned(span.shrink_to_after(colon_index as _)), }); }; let Some(closing_bracket) = tokens.next_if_closing_bracket() else { return Err(Error { _inner: unused(opening_bracket.error("unclosed bracket")), public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { index: opening_bracket.byte as _, }, }); }; Ok(Item::Component { _opening_bracket: unused(opening_bracket), _leading_whitespace: unused(leading_whitespace), name, modifiers: modifiers.into_boxed_slice(), _trailing_whitespace: unused(trailing_whitespace), _closing_bracket: unused(closing_bracket), }) } /// Parse a nested format description. The location provided is the the most recent one consumed. fn parse_nested<'a, I: Iterator, Error>>, const VERSION: usize>( last_location: Location, tokens: &mut lexer::Lexed, ) -> Result, Error> { validate_version!(VERSION); let Some(opening_bracket) = tokens.next_if_opening_bracket() else { return Err(Error { _inner: unused(last_location.error("expected opening bracket")), public: crate::error::InvalidFormatDescription::Expected { what: "opening bracket", index: last_location.byte as _, }, }); }; let items = parse_inner::<_, true, VERSION>(tokens).collect::>()?; let Some(closing_bracket) = tokens.next_if_closing_bracket() else { return Err(Error { _inner: unused(opening_bracket.error("unclosed bracket")), public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { index: opening_bracket.byte as _, }, }); }; let trailing_whitespace = tokens.next_if_whitespace(); Ok(NestedFormatDescription { _opening_bracket: unused(opening_bracket), items, _closing_bracket: unused(closing_bracket), _trailing_whitespace: unused(trailing_whitespace), }) } time-0.3.37/src/format_description/parse/format_item.rs000064400000000000000000000454711046102023000213370ustar 00000000000000//! Typed, validated representation of a parsed format description. use alloc::boxed::Box; use alloc::string::String; use core::num::NonZeroU16; use core::str::{self, FromStr}; use super::{ast, unused, Error, Span, Spanned}; use crate::internal_macros::bug; /// Parse an AST iterator into a sequence of format items. pub(super) fn parse<'a>( ast_items: impl Iterator, Error>>, ) -> impl Iterator, Error>> { ast_items.map(|ast_item| ast_item.and_then(Item::from_ast)) } /// A description of how to format and parse one part of a type. pub(super) enum Item<'a> { /// A literal string. Literal(&'a [u8]), /// Part of a type, along with its modifiers. Component(Component), /// A sequence of optional items. Optional { /// The items themselves. value: Box<[Self]>, /// The span of the full sequence. span: Span, }, /// The first matching parse of a sequence of format descriptions. First { /// The sequence of format descriptions. value: Box<[Box<[Self]>]>, /// The span of the full sequence. span: Span, }, } impl Item<'_> { /// Parse an AST item into a format item. pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result, Error> { Ok(match ast_item { ast::Item::Component { _opening_bracket: _, _leading_whitespace: _, name, modifiers, _trailing_whitespace: _, _closing_bracket: _, } => Item::Component(component_from_ast(&name, &modifiers)?), ast::Item::Literal(Spanned { value, span: _ }) => Item::Literal(value), ast::Item::EscapedBracket { _first: _, _second: _, } => Item::Literal(b"["), ast::Item::Optional { opening_bracket, _leading_whitespace: _, _optional_kw: _, _whitespace: _, nested_format_description, closing_bracket, } => { let items = nested_format_description .items .into_vec() .into_iter() .map(Item::from_ast) .collect::>()?; Item::Optional { value: items, span: opening_bracket.to(closing_bracket), } } ast::Item::First { opening_bracket, _leading_whitespace: _, _first_kw: _, _whitespace: _, nested_format_descriptions, closing_bracket, } => { let items = nested_format_descriptions .into_vec() .into_iter() .map(|nested_format_description| { nested_format_description .items .into_vec() .into_iter() .map(Item::from_ast) .collect() }) .collect::>()?; Item::First { value: items, span: opening_bracket.to(closing_bracket), } } }) } } impl<'a> TryFrom> for crate::format_description::BorrowedFormatItem<'a> { type Error = Error; fn try_from(item: Item<'a>) -> Result { match item { Item::Literal(literal) => Ok(Self::Literal(literal)), Item::Component(component) => Ok(Self::Component(component.into())), Item::Optional { value: _, span } => Err(Error { _inner: unused(span.error( "optional items are not supported in runtime-parsed format descriptions", )), public: crate::error::InvalidFormatDescription::NotSupported { what: "optional item", context: "runtime-parsed format descriptions", index: span.start.byte as _, }, }), Item::First { value: _, span } => Err(Error { _inner: unused(span.error( "'first' items are not supported in runtime-parsed format descriptions", )), public: crate::error::InvalidFormatDescription::NotSupported { what: "'first' item", context: "runtime-parsed format descriptions", index: span.start.byte as _, }, }), } } } impl From> for crate::format_description::OwnedFormatItem { fn from(item: Item<'_>) -> Self { match item { Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()), Item::Component(component) => Self::Component(component.into()), Item::Optional { value, span: _ } => Self::Optional(Box::new(value.into())), Item::First { value, span: _ } => { Self::First(value.into_vec().into_iter().map(Into::into).collect()) } } } } impl<'a> From]>> for crate::format_description::OwnedFormatItem { fn from(items: Box<[Item<'a>]>) -> Self { let items = items.into_vec(); match <[_; 1]>::try_from(items) { Ok([item]) => item.into(), Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()), } } } /// Declare the `Component` struct. macro_rules! component_definition { (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* }; (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? }; (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* }; (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? }; ($vis:vis enum $name:ident { $($variant:ident = $parse_variant:literal {$( $(#[$required:tt])? $field:ident = $parse_field:literal: Option<$(#[$from_str:tt])? $field_type:ty> => $target_field:ident ),* $(,)?}),* $(,)? }) => { $vis enum $name { $($variant($variant),)* } $($vis struct $variant { $($field: Option<$field_type>),* })* $(impl $variant { /// Parse the component from the AST, given its modifiers. fn with_modifiers( modifiers: &[ast::Modifier<'_>], _component_span: Span, ) -> Result { // rustc will complain if the modifier is empty. #[allow(unused_mut)] let mut this = Self { $($field: None),* }; for modifier in modifiers { $(#[allow(clippy::string_lit_as_bytes)] if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) { this.$field = component_definition!(@if_from_str $($from_str)? then { parse_from_modifier_value::<$field_type>(&modifier.value)? } else { <$field_type>::from_modifier_value(&modifier.value)? }); continue; })* return Err(Error { _inner: unused(modifier.key.span.error("invalid modifier key")), public: crate::error::InvalidFormatDescription::InvalidModifier { value: String::from_utf8_lossy(*modifier.key).into_owned(), index: modifier.key.span.start.byte as _, } }); } $(component_definition! { @if_required $($required)? then { if this.$field.is_none() { return Err(Error { _inner: unused(_component_span.error("missing required modifier")), public: crate::error::InvalidFormatDescription::MissingRequiredModifier { name: $parse_field, index: _component_span.start.byte as _, } }); } }})* Ok(this) } })* impl From<$name> for crate::format_description::Component { fn from(component: $name) -> Self { match component {$( $name::$variant($variant { $($field),* }) => { $crate::format_description::component::Component::$variant( $crate::format_description::modifier::$variant {$( $target_field: component_definition! { @if_required $($required)? then { match $field { Some(value) => value.into(), None => bug!("required modifier was not set"), } } else { $field.unwrap_or_default().into() } } ),*} ) } )*} } } /// Parse a component from the AST, given its name and modifiers. fn component_from_ast( name: &Spanned<&[u8]>, modifiers: &[ast::Modifier<'_>], ) -> Result { $(#[allow(clippy::string_lit_as_bytes)] if name.eq_ignore_ascii_case($parse_variant.as_bytes()) { return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?)); })* Err(Error { _inner: unused(name.span.error("invalid component")), public: crate::error::InvalidFormatDescription::InvalidComponentName { name: String::from_utf8_lossy(name).into_owned(), index: name.span.start.byte as _, }, }) } } } // Keep in alphabetical order. component_definition! { pub(super) enum Component { Day = "day" { padding = "padding": Option => padding, }, End = "end" {}, Hour = "hour" { padding = "padding": Option => padding, base = "repr": Option => is_12_hour_clock, }, Ignore = "ignore" { #[required] count = "count": Option<#[from_str] NonZeroU16> => count, }, Minute = "minute" { padding = "padding": Option => padding, }, Month = "month" { padding = "padding": Option => padding, repr = "repr": Option => repr, case_sensitive = "case_sensitive": Option => case_sensitive, }, OffsetHour = "offset_hour" { sign_behavior = "sign": Option => sign_is_mandatory, padding = "padding": Option => padding, }, OffsetMinute = "offset_minute" { padding = "padding": Option => padding, }, OffsetSecond = "offset_second" { padding = "padding": Option => padding, }, Ordinal = "ordinal" { padding = "padding": Option => padding, }, Period = "period" { case = "case": Option => is_uppercase, case_sensitive = "case_sensitive": Option => case_sensitive, }, Second = "second" { padding = "padding": Option => padding, }, Subsecond = "subsecond" { digits = "digits": Option => digits, }, UnixTimestamp = "unix_timestamp" { precision = "precision": Option => precision, sign_behavior = "sign": Option => sign_is_mandatory, }, Weekday = "weekday" { repr = "repr": Option => repr, one_indexed = "one_indexed": Option => one_indexed, case_sensitive = "case_sensitive": Option => case_sensitive, }, WeekNumber = "week_number" { padding = "padding": Option => padding, repr = "repr": Option => repr, }, Year = "year" { padding = "padding": Option => padding, repr = "repr": Option => repr, base = "base": Option => iso_week_based, sign_behavior = "sign": Option => sign_is_mandatory, }, } } /// Get the target type for a given enum. macro_rules! target_ty { ($name:ident $type:ty) => { $type }; ($name:ident) => { $crate::format_description::modifier::$name }; } /// Get the target value for a given enum. macro_rules! target_value { ($name:ident $variant:ident $value:expr) => { $value }; ($name:ident $variant:ident) => { $crate::format_description::modifier::$name::$variant }; } /// Declare the various modifiers. /// /// For the general case, ordinary syntax can be used. Note that you _must_ declare a default /// variant. The only significant change is that the string representation of the variant must be /// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant /// named `Numerical` with the string representation `b"numerical"`. This is the value that will be /// used when parsing the modifier. The value is not case sensitive. /// /// If the type in the public API does not have the same name as the type in the internal /// representation, then the former must be specified in parenthesis after the internal name. For /// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in /// the public API. /// /// By default, the internal variant name is assumed to be the same as the public variant name. If /// this is not the case, the qualified path to the variant must be specified in parenthesis after /// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve", /// but is represented as `true` in the public API. macro_rules! modifier { ($( enum $name:ident $(($target_ty:ty))? { $( $(#[$attr:meta])? $variant:ident $(($target_value:expr))? = $parse_variant:literal ),* $(,)? } )+) => {$( #[derive(Default)] enum $name { $($(#[$attr])? $variant),* } impl $name { /// Parse the modifier from its string representation. fn from_modifier_value(value: &Spanned<&[u8]>) -> Result, Error> { $(if value.eq_ignore_ascii_case($parse_variant) { return Ok(Some(Self::$variant)); })* Err(Error { _inner: unused(value.span.error("invalid modifier value")), public: crate::error::InvalidFormatDescription::InvalidModifier { value: String::from_utf8_lossy(value).into_owned(), index: value.span.start.byte as _, }, }) } } impl From<$name> for target_ty!($name $($target_ty)?) { fn from(modifier: $name) -> Self { match modifier { $($name::$variant => target_value!($name $variant $($target_value)?)),* } } } )+}; } // Keep in alphabetical order. modifier! { enum HourBase(bool) { Twelve(true) = b"12", #[default] TwentyFour(false) = b"24", } enum MonthCaseSensitive(bool) { False(false) = b"false", #[default] True(true) = b"true", } enum MonthRepr { #[default] Numerical = b"numerical", Long = b"long", Short = b"short", } enum Padding { Space = b"space", #[default] Zero = b"zero", None = b"none", } enum PeriodCase(bool) { Lower(false) = b"lower", #[default] Upper(true) = b"upper", } enum PeriodCaseSensitive(bool) { False(false) = b"false", #[default] True(true) = b"true", } enum SignBehavior(bool) { #[default] Automatic(false) = b"automatic", Mandatory(true) = b"mandatory", } enum SubsecondDigits { One = b"1", Two = b"2", Three = b"3", Four = b"4", Five = b"5", Six = b"6", Seven = b"7", Eight = b"8", Nine = b"9", #[default] OneOrMore = b"1+", } enum UnixTimestampPrecision { #[default] Second = b"second", Millisecond = b"millisecond", Microsecond = b"microsecond", Nanosecond = b"nanosecond", } enum WeekNumberRepr { #[default] Iso = b"iso", Sunday = b"sunday", Monday = b"monday", } enum WeekdayCaseSensitive(bool) { False(false) = b"false", #[default] True(true) = b"true", } enum WeekdayOneIndexed(bool) { False(false) = b"false", #[default] True(true) = b"true", } enum WeekdayRepr { Short = b"short", #[default] Long = b"long", Sunday = b"sunday", Monday = b"monday", } enum YearBase(bool) { #[default] Calendar(false) = b"calendar", IsoWeek(true) = b"iso_week", } enum YearRepr { #[default] Full = b"full", Century = b"century", LastTwo = b"last_two", } } /// Parse a modifier value using `FromStr`. Requires the modifier value to be valid UTF-8. fn parse_from_modifier_value(value: &Spanned<&[u8]>) -> Result, Error> { str::from_utf8(value) .ok() .and_then(|val| val.parse::().ok()) .map(|val| Some(val)) .ok_or_else(|| Error { _inner: unused(value.span.error("invalid modifier value")), public: crate::error::InvalidFormatDescription::InvalidModifier { value: String::from_utf8_lossy(value).into_owned(), index: value.span.start.byte as _, }, }) } time-0.3.37/src/format_description/parse/lexer.rs000064400000000000000000000227251046102023000201450ustar 00000000000000//! Lexer for parsing format descriptions. use core::iter; use super::{attach_location, unused, Error, Location, Spanned, SpannedValue}; /// An iterator over the lexed tokens. pub(super) struct Lexed { /// The internal iterator. iter: iter::Peekable, } impl Iterator for Lexed { type Item = I::Item; fn next(&mut self) -> Option { self.iter.next() } } impl<'iter, 'token: 'iter, I: Iterator, Error>> + 'iter> Lexed { /// Peek at the next item in the iterator. pub(super) fn peek(&mut self) -> Option<&I::Item> { self.iter.peek() } /// Consume the next token if it is whitespace. pub(super) fn next_if_whitespace(&mut self) -> Option> { if let Some(&Ok(Token::ComponentPart { kind: ComponentKind::Whitespace, value, })) = self.peek() { self.next(); // consume Some(value) } else { None } } /// Consume the next token if it is a component item that is not whitespace. pub(super) fn next_if_not_whitespace(&mut self) -> Option> { if let Some(&Ok(Token::ComponentPart { kind: ComponentKind::NotWhitespace, value, })) = self.peek() { self.next(); // consume Some(value) } else { None } } /// Consume the next token if it is an opening bracket. pub(super) fn next_if_opening_bracket(&mut self) -> Option { if let Some(&Ok(Token::Bracket { kind: BracketKind::Opening, location, })) = self.peek() { self.next(); // consume Some(location) } else { None } } /// Peek at the next token if it is a closing bracket. pub(super) fn peek_closing_bracket(&'iter mut self) -> Option<&'iter Location> { if let Some(Ok(Token::Bracket { kind: BracketKind::Closing, location, })) = self.peek() { Some(location) } else { None } } /// Consume the next token if it is a closing bracket. pub(super) fn next_if_closing_bracket(&mut self) -> Option { if let Some(&Ok(Token::Bracket { kind: BracketKind::Closing, location, })) = self.peek() { self.next(); // consume Some(location) } else { None } } } /// A token emitted by the lexer. There is no semantic meaning at this stage. pub(super) enum Token<'a> { /// A literal string, formatted and parsed as-is. Literal(Spanned<&'a [u8]>), /// An opening or closing bracket. May or may not be the start or end of a component. Bracket { /// Whether the bracket is opening or closing. kind: BracketKind, /// Where the bracket was in the format string. location: Location, }, /// One part of a component. This could be its name, a modifier, or whitespace. ComponentPart { /// Whether the part is whitespace or not. kind: ComponentKind, /// The part itself. value: Spanned<&'a [u8]>, }, } /// What type of bracket is present. pub(super) enum BracketKind { /// An opening bracket: `[` Opening, /// A closing bracket: `]` Closing, } /// Indicates whether the component is whitespace or not. pub(super) enum ComponentKind { #[allow(clippy::missing_docs_in_private_items)] Whitespace, #[allow(clippy::missing_docs_in_private_items)] NotWhitespace, } /// Parse the string into a series of [`Token`]s. /// /// `VERSION` controls the version of the format description that is being parsed. Currently, this /// must be 1 or 2. /// /// - When `VERSION` is 1, `[[` is the only escape sequence, resulting in a literal `[`. /// - When `VERSION` is 2, all escape sequences begin with `\`. The only characters that may /// currently follow are `\`, `[`, and `]`, all of which result in the literal character. All /// other characters result in a lex error. pub(super) fn lex( mut input: &[u8], ) -> Lexed, Error>>> { validate_version!(VERSION); let mut depth: u8 = 0; let mut iter = attach_location(input.iter()).peekable(); let mut second_bracket_location = None; let iter = iter::from_fn(move || { // The flag is only set when version is zero. if version!(..=1) { // There is a flag set to emit the second half of an escaped bracket pair. if let Some(location) = second_bracket_location.take() { return Some(Ok(Token::Bracket { kind: BracketKind::Opening, location, })); } } Some(Ok(match iter.next()? { // possible escape sequence (b'\\', backslash_loc) if version!(2..) => { match iter.next() { Some((b'\\' | b'[' | b']', char_loc)) => { // The escaped character is emitted as-is. let char = &input[1..2]; input = &input[2..]; if depth == 0 { Token::Literal(char.spanned(backslash_loc.to(char_loc))) } else { Token::ComponentPart { kind: ComponentKind::NotWhitespace, value: char.spanned(backslash_loc.to(char_loc)), } } } Some((_, loc)) => { return Some(Err(Error { _inner: unused(loc.error("invalid escape sequence")), public: crate::error::InvalidFormatDescription::Expected { what: "valid escape sequence", index: loc.byte as _, }, })); } None => { return Some(Err(Error { _inner: unused(backslash_loc.error("unexpected end of input")), public: crate::error::InvalidFormatDescription::Expected { what: "valid escape sequence", index: backslash_loc.byte as _, }, })); } } } // potentially escaped opening bracket (b'[', location) if version!(..=1) => { if let Some((_, second_location)) = iter.next_if(|&(&byte, _)| byte == b'[') { // Escaped bracket. Store the location of the second so we can emit it later. second_bracket_location = Some(second_location); input = &input[2..]; } else { // opening bracket depth += 1; input = &input[1..]; } Token::Bracket { kind: BracketKind::Opening, location, } } // opening bracket (b'[', location) => { depth += 1; input = &input[1..]; Token::Bracket { kind: BracketKind::Opening, location, } } // closing bracket (b']', location) if depth > 0 => { depth -= 1; input = &input[1..]; Token::Bracket { kind: BracketKind::Closing, location, } } // literal (_, start_location) if depth == 0 => { let mut bytes = 1; let mut end_location = start_location; while let Some((_, location)) = iter.next_if(|&(&byte, _)| !((version!(2..) && byte == b'\\') || byte == b'[')) { end_location = location; bytes += 1; } let value = &input[..bytes]; input = &input[bytes..]; Token::Literal(value.spanned(start_location.to(end_location))) } // component part (byte, start_location) => { let mut bytes = 1; let mut end_location = start_location; let is_whitespace = byte.is_ascii_whitespace(); while let Some((_, location)) = iter.next_if(|&(byte, _)| { !matches!(byte, b'\\' | b'[' | b']') && is_whitespace == byte.is_ascii_whitespace() }) { end_location = location; bytes += 1; } let value = &input[..bytes]; input = &input[bytes..]; Token::ComponentPart { kind: if is_whitespace { ComponentKind::Whitespace } else { ComponentKind::NotWhitespace }, value: value.spanned(start_location.to(end_location)), } } })) }); Lexed { iter: iter.peekable(), } } time-0.3.37/src/format_description/parse/mod.rs000064400000000000000000000176621046102023000176110ustar 00000000000000//! Parser for format descriptions. use alloc::boxed::Box; use alloc::vec::Vec; pub use self::strftime::{parse_strftime_borrowed, parse_strftime_owned}; use crate::{error, format_description}; /// A helper macro to make version restrictions simpler to read and write. macro_rules! version { ($range:expr) => { $range.contains(&VERSION) }; } /// A helper macro to statically validate the version (when used as a const parameter). macro_rules! validate_version { ($version:ident) => { #[allow(clippy::let_unit_value)] let _ = $crate::format_description::parse::Version::<$version>::IS_VALID; }; } mod ast; mod format_item; mod lexer; mod strftime; /// A struct that is used to ensure that the version is valid. struct Version; impl Version { /// A constant that panics if the version is not valid. This results in a post-monomorphization /// error. const IS_VALID: () = assert!(N >= 1 && N <= 2); } /// Parse a sequence of items from the format description. /// /// The syntax for the format description can be found in [the /// book](https://time-rs.github.io/book/api/format-description.html). /// /// This function exists for backward compatibility reasons. It is equivalent to calling /// `parse_borrowed::<1>(s)`. In the future, this function will be deprecated in favor of /// `parse_borrowed`. pub fn parse( s: &str, ) -> Result>, error::InvalidFormatDescription> { parse_borrowed::<1>(s) } /// Parse a sequence of items from the format description. /// /// The syntax for the format description can be found in [the /// book](https://time-rs.github.io/book/api/format-description.html). The version of the format /// description is provided as the const parameter. **It is recommended to use version 2.** pub fn parse_borrowed( s: &str, ) -> Result>, error::InvalidFormatDescription> { validate_version!(VERSION); let mut lexed = lexer::lex::(s.as_bytes()); let ast = ast::parse::<_, VERSION>(&mut lexed); let format_items = format_item::parse(ast); Ok(format_items .map(|res| res.and_then(TryInto::try_into)) .collect::>()?) } /// Parse a sequence of items from the format description. /// /// The syntax for the format description can be found in [the /// book](https://time-rs.github.io/book/api/format-description.html). The version of the format /// description is provided as the const parameter. /// /// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means /// that there is no lifetime that needs to be handled. **It is recommended to use version 2.** /// /// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem pub fn parse_owned( s: &str, ) -> Result { validate_version!(VERSION); let mut lexed = lexer::lex::(s.as_bytes()); let ast = ast::parse::<_, VERSION>(&mut lexed); let format_items = format_item::parse(ast); let items = format_items.collect::, _>>()?; Ok(items.into()) } /// Attach [`Location`] information to each byte in the iterator. fn attach_location<'item>( iter: impl Iterator, ) -> impl Iterator { let mut byte_pos = 0; iter.map(move |byte| { let location = Location { byte: byte_pos }; byte_pos += 1; (byte, location) }) } /// A location within a string. #[derive(Clone, Copy)] struct Location { /// The zero-indexed byte of the string. byte: u32, } impl Location { /// Create a new [`Span`] from `self` to `other`. const fn to(self, end: Self) -> Span { Span { start: self, end } } /// Create a new [`Span`] consisting entirely of `self`. const fn to_self(self) -> Span { Span { start: self, end: self, } } /// Offset the location by the provided amount. /// /// Note that this assumes the resulting location is on the same line as the original location. #[must_use = "this does not modify the original value"] const fn offset(&self, offset: u32) -> Self { Self { byte: self.byte + offset, } } /// Create an error with the provided message at this location. const fn error(self, message: &'static str) -> ErrorInner { ErrorInner { _message: message, _span: Span { start: self, end: self, }, } } } /// A start and end point within a string. #[derive(Clone, Copy)] struct Span { #[allow(clippy::missing_docs_in_private_items)] start: Location, #[allow(clippy::missing_docs_in_private_items)] end: Location, } impl Span { /// Obtain a `Span` pointing at the start of the pre-existing span. #[must_use = "this does not modify the original value"] const fn shrink_to_start(&self) -> Self { Self { start: self.start, end: self.start, } } /// Obtain a `Span` pointing at the end of the pre-existing span. #[must_use = "this does not modify the original value"] const fn shrink_to_end(&self) -> Self { Self { start: self.end, end: self.end, } } /// Obtain a `Span` that ends before the provided position of the pre-existing span. #[must_use = "this does not modify the original value"] const fn shrink_to_before(&self, pos: u32) -> Self { Self { start: self.start, end: Location { byte: self.start.byte + pos - 1, }, } } /// Obtain a `Span` that starts after provided position to the end of the pre-existing span. #[must_use = "this does not modify the original value"] const fn shrink_to_after(&self, pos: u32) -> Self { Self { start: Location { byte: self.start.byte + pos + 1, }, end: self.end, } } /// Create an error with the provided message at this span. const fn error(self, message: &'static str) -> ErrorInner { ErrorInner { _message: message, _span: self, } } } /// A value with an associated [`Span`]. #[derive(Clone, Copy)] struct Spanned { /// The value. value: T, /// Where the value was in the format string. span: Span, } impl core::ops::Deref for Spanned { type Target = T; fn deref(&self) -> &Self::Target { &self.value } } /// Helper trait to attach a [`Span`] to a value. trait SpannedValue: Sized { /// Attach a [`Span`] to a value. fn spanned(self, span: Span) -> Spanned; } impl SpannedValue for T { fn spanned(self, span: Span) -> Spanned { Spanned { value: self, span } } } /// The internal error type. struct ErrorInner { /// The message displayed to the user. _message: &'static str, /// Where the error originated. _span: Span, } /// A complete error description. struct Error { /// The internal error. _inner: Unused, /// The error needed for interoperability with the rest of `time`. public: error::InvalidFormatDescription, } impl From for error::InvalidFormatDescription { fn from(error: Error) -> Self { error.public } } /// A value that may be used in the future, but currently is not. /// /// This struct exists so that data can semantically be passed around without _actually_ passing it /// around. This way the data still exists if it is needed in the future. // `PhantomData` is not used directly because we don't want to introduce any trait implementations. struct Unused(core::marker::PhantomData); /// Indicate that a value is currently unused. fn unused(_: T) -> Unused { Unused(core::marker::PhantomData) } time-0.3.37/src/format_description/parse/strftime.rs000064400000000000000000000416001046102023000206540ustar 00000000000000use alloc::string::String; use alloc::vec::Vec; use core::iter; use crate::error::InvalidFormatDescription; use crate::format_description::parse::{ attach_location, unused, Error, ErrorInner, Location, Spanned, SpannedValue, Unused, }; use crate::format_description::{self, modifier, BorrowedFormatItem, Component}; /// Parse a sequence of items from the [`strftime` format description][strftime docs]. /// /// The only heap allocation required is for the `Vec` itself. All components are bound to the /// lifetime of the input. /// /// [strftime docs]: https://man7.org/linux/man-pages/man3/strftime.3.html #[doc(alias = "parse_strptime_borrowed")] pub fn parse_strftime_borrowed( s: &str, ) -> Result>, InvalidFormatDescription> { let tokens = lex(s.as_bytes()); let items = into_items(tokens).collect::>()?; Ok(items) } /// Parse a sequence of items from the [`strftime` format description][strftime docs]. /// /// This requires heap allocation for some owned items. /// /// [strftime docs]: https://man7.org/linux/man-pages/man3/strftime.3.html #[doc(alias = "parse_strptime_owned")] pub fn parse_strftime_owned( s: &str, ) -> Result { parse_strftime_borrowed(s).map(Into::into) } #[derive(Debug, Clone, Copy, PartialEq)] enum Padding { /// The default padding for a numeric component. Indicated by no character. Default, /// Pad a numeric component with spaces. Indicated by an underscore. Spaces, /// Do not pad a numeric component. Indicated by a hyphen. None, /// Pad a numeric component with zeroes. Indicated by a zero. Zeroes, } enum Token<'a> { Literal(Spanned<&'a [u8]>), Component { _percent: Unused, padding: Spanned, component: Spanned, }, } fn lex(mut input: &[u8]) -> iter::Peekable, Error>>> { let mut iter = attach_location(input.iter()).peekable(); iter::from_fn(move || { Some(Ok(match iter.next()? { (b'%', percent_loc) => match iter.next() { Some((padding @ (b'_' | b'-' | b'0'), padding_loc)) => { let padding = match padding { b'_' => Padding::Spaces, b'-' => Padding::None, b'0' => Padding::Zeroes, _ => unreachable!(), }; let (&component, component_loc) = iter.next()?; input = &input[3..]; Token::Component { _percent: unused(percent_loc), padding: padding.spanned(padding_loc.to_self()), component: component.spanned(component_loc.to_self()), } } Some((&component, component_loc)) => { input = &input[2..]; let span = component_loc.to_self(); Token::Component { _percent: unused(percent_loc), padding: Padding::Default.spanned(span), component: component.spanned(span), } } None => { return Some(Err(Error { _inner: unused(percent_loc.error("unexpected end of input")), public: InvalidFormatDescription::Expected { what: "valid escape sequence", index: percent_loc.byte as _, }, })); } }, (_, start_location) => { 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.spanned(start_location.to(end_location))) } })) }) .peekable() } fn into_items<'iter, 'token: 'iter>( mut tokens: iter::Peekable, Error>> + 'iter>, ) -> impl Iterator, Error>> + 'iter { iter::from_fn(move || { let next = match tokens.next()? { Ok(token) => token, Err(err) => return Some(Err(err)), }; Some(match next { Token::Literal(spanned) => Ok(BorrowedFormatItem::Literal(*spanned)), Token::Component { _percent, padding, component, } => parse_component(padding, component), }) }) } fn parse_component( padding: Spanned, component: Spanned, ) -> Result, Error> { let padding_or_default = |padding: Padding, default| match padding { Padding::Default => default, Padding::Spaces => modifier::Padding::Space, Padding::None => modifier::Padding::None, Padding::Zeroes => modifier::Padding::Zero, }; /// Helper macro to create a component. macro_rules! component { ($name:ident { $($inner:tt)* }) => { BorrowedFormatItem::Component(Component::$name(modifier::$name { $($inner)* })) } } Ok(match *component { b'%' => BorrowedFormatItem::Literal(b"%"), b'a' => component!(Weekday { repr: modifier::WeekdayRepr::Short, one_indexed: true, case_sensitive: true, }), b'A' => component!(Weekday { repr: modifier::WeekdayRepr::Long, one_indexed: true, case_sensitive: true, }), b'b' | b'h' => component!(Month { repr: modifier::MonthRepr::Short, padding: modifier::Padding::Zero, case_sensitive: true, }), b'B' => component!(Month { repr: modifier::MonthRepr::Long, padding: modifier::Padding::Zero, case_sensitive: true, }), b'c' => BorrowedFormatItem::Compound(&[ component!(Weekday { repr: modifier::WeekdayRepr::Short, one_indexed: true, case_sensitive: true, }), BorrowedFormatItem::Literal(b" "), component!(Month { repr: modifier::MonthRepr::Short, padding: modifier::Padding::Zero, case_sensitive: true, }), BorrowedFormatItem::Literal(b" "), component!(Day { padding: modifier::Padding::Space }), BorrowedFormatItem::Literal(b" "), component!(Hour { padding: modifier::Padding::Zero, is_12_hour_clock: false, }), BorrowedFormatItem::Literal(b":"), component!(Minute { padding: modifier::Padding::Zero, }), BorrowedFormatItem::Literal(b":"), component!(Second { padding: modifier::Padding::Zero, }), BorrowedFormatItem::Literal(b" "), component!(Year { padding: modifier::Padding::Zero, repr: modifier::YearRepr::Full, iso_week_based: false, sign_is_mandatory: false, }), ]), b'C' => component!(Year { padding: padding_or_default(*padding, modifier::Padding::Zero), repr: modifier::YearRepr::Century, iso_week_based: false, sign_is_mandatory: false, }), b'd' => component!(Day { padding: padding_or_default(*padding, modifier::Padding::Zero), }), b'D' => BorrowedFormatItem::Compound(&[ component!(Month { repr: modifier::MonthRepr::Numerical, padding: modifier::Padding::Zero, case_sensitive: true, }), BorrowedFormatItem::Literal(b"/"), component!(Day { padding: modifier::Padding::Zero, }), BorrowedFormatItem::Literal(b"/"), component!(Year { padding: modifier::Padding::Zero, repr: modifier::YearRepr::LastTwo, iso_week_based: false, sign_is_mandatory: false, }), ]), b'e' => component!(Day { padding: padding_or_default(*padding, modifier::Padding::Space), }), b'F' => BorrowedFormatItem::Compound(&[ component!(Year { padding: modifier::Padding::Zero, repr: modifier::YearRepr::Full, iso_week_based: false, sign_is_mandatory: false, }), BorrowedFormatItem::Literal(b"-"), component!(Month { padding: modifier::Padding::Zero, repr: modifier::MonthRepr::Numerical, case_sensitive: true, }), BorrowedFormatItem::Literal(b"-"), component!(Day { padding: modifier::Padding::Zero, }), ]), b'g' => component!(Year { padding: padding_or_default(*padding, modifier::Padding::Zero), repr: modifier::YearRepr::LastTwo, iso_week_based: true, sign_is_mandatory: false, }), b'G' => component!(Year { padding: modifier::Padding::Zero, repr: modifier::YearRepr::Full, iso_week_based: true, sign_is_mandatory: false, }), b'H' => component!(Hour { padding: padding_or_default(*padding, modifier::Padding::Zero), is_12_hour_clock: false, }), b'I' => component!(Hour { padding: padding_or_default(*padding, modifier::Padding::Zero), is_12_hour_clock: true, }), b'j' => component!(Ordinal { padding: padding_or_default(*padding, modifier::Padding::Zero), }), b'k' => component!(Hour { padding: padding_or_default(*padding, modifier::Padding::Space), is_12_hour_clock: false, }), b'l' => component!(Hour { padding: padding_or_default(*padding, modifier::Padding::Space), is_12_hour_clock: true, }), b'm' => component!(Month { padding: padding_or_default(*padding, modifier::Padding::Zero), repr: modifier::MonthRepr::Numerical, case_sensitive: true, }), b'M' => component!(Minute { padding: padding_or_default(*padding, modifier::Padding::Zero), }), b'n' => BorrowedFormatItem::Literal(b"\n"), b'O' => { return Err(Error { _inner: unused(ErrorInner { _message: "unsupported modifier", _span: component.span, }), public: InvalidFormatDescription::NotSupported { what: "modifier", context: "", index: component.span.start.byte as _, }, }) } b'p' => component!(Period { is_uppercase: true, case_sensitive: true }), b'P' => component!(Period { is_uppercase: false, case_sensitive: true }), b'r' => BorrowedFormatItem::Compound(&[ component!(Hour { padding: modifier::Padding::Zero, is_12_hour_clock: true, }), BorrowedFormatItem::Literal(b":"), component!(Minute { padding: modifier::Padding::Zero, }), BorrowedFormatItem::Literal(b":"), component!(Second { padding: modifier::Padding::Zero, }), BorrowedFormatItem::Literal(b" "), component!(Period { is_uppercase: true, case_sensitive: true, }), ]), b'R' => BorrowedFormatItem::Compound(&[ component!(Hour { padding: modifier::Padding::Zero, is_12_hour_clock: false, }), BorrowedFormatItem::Literal(b":"), component!(Minute { padding: modifier::Padding::Zero, }), ]), b's' => component!(UnixTimestamp { precision: modifier::UnixTimestampPrecision::Second, sign_is_mandatory: false, }), b'S' => component!(Second { padding: padding_or_default(*padding, modifier::Padding::Zero), }), b't' => BorrowedFormatItem::Literal(b"\t"), b'T' => BorrowedFormatItem::Compound(&[ component!(Hour { padding: modifier::Padding::Zero, is_12_hour_clock: false, }), BorrowedFormatItem::Literal(b":"), component!(Minute { padding: modifier::Padding::Zero, }), BorrowedFormatItem::Literal(b":"), component!(Second { padding: modifier::Padding::Zero, }), ]), b'u' => component!(Weekday { repr: modifier::WeekdayRepr::Monday, one_indexed: true, case_sensitive: true, }), b'U' => component!(WeekNumber { padding: padding_or_default(*padding, modifier::Padding::Zero), repr: modifier::WeekNumberRepr::Sunday, }), b'V' => component!(WeekNumber { padding: padding_or_default(*padding, modifier::Padding::Zero), repr: modifier::WeekNumberRepr::Iso, }), b'w' => component!(Weekday { repr: modifier::WeekdayRepr::Sunday, one_indexed: true, case_sensitive: true, }), b'W' => component!(WeekNumber { padding: padding_or_default(*padding, modifier::Padding::Zero), repr: modifier::WeekNumberRepr::Monday, }), b'x' => BorrowedFormatItem::Compound(&[ component!(Month { repr: modifier::MonthRepr::Numerical, padding: modifier::Padding::Zero, case_sensitive: true, }), BorrowedFormatItem::Literal(b"/"), component!(Day { padding: modifier::Padding::Zero }), BorrowedFormatItem::Literal(b"/"), component!(Year { padding: modifier::Padding::Zero, repr: modifier::YearRepr::LastTwo, iso_week_based: false, sign_is_mandatory: false, }), ]), b'X' => BorrowedFormatItem::Compound(&[ component!(Hour { padding: modifier::Padding::Zero, is_12_hour_clock: false, }), BorrowedFormatItem::Literal(b":"), component!(Minute { padding: modifier::Padding::Zero, }), BorrowedFormatItem::Literal(b":"), component!(Second { padding: modifier::Padding::Zero, }), ]), b'y' => component!(Year { padding: padding_or_default(*padding, modifier::Padding::Zero), repr: modifier::YearRepr::LastTwo, iso_week_based: false, sign_is_mandatory: false, }), b'Y' => component!(Year { padding: modifier::Padding::Zero, repr: modifier::YearRepr::Full, iso_week_based: false, sign_is_mandatory: false, }), b'z' => BorrowedFormatItem::Compound(&[ component!(OffsetHour { sign_is_mandatory: true, padding: modifier::Padding::Zero, }), component!(OffsetMinute { padding: modifier::Padding::Zero, }), ]), b'Z' => { return Err(Error { _inner: unused(ErrorInner { _message: "unsupported component", _span: component.span, }), public: InvalidFormatDescription::NotSupported { what: "component", context: "", index: component.span.start.byte as _, }, }) } _ => { return Err(Error { _inner: unused(ErrorInner { _message: "invalid component", _span: component.span, }), public: InvalidFormatDescription::InvalidComponentName { name: String::from_utf8_lossy(&[*component]).into_owned(), index: component.span.start.byte as _, }, }) } }) } time-0.3.37/src/format_description/well_known/iso8601/adt_hack.rs000064400000000000000000000230441046102023000227350ustar 00000000000000//! Hackery to work around not being able to use ADTs in const generics on stable. use core::num::NonZeroU8; #[cfg(feature = "formatting")] use super::Iso8601; use super::{Config, DateKind, FormattedComponents as FC, OffsetPrecision, TimePrecision}; // This provides a way to include `EncodedConfig` in documentation without displaying the type it is // aliased to. #[doc(hidden)] pub type DoNotRelyOnWhatThisIs = u128; /// An encoded [`Config`] that can be used as a const parameter to [`Iso8601`](super::Iso8601). /// /// The type this is aliased to must not be relied upon. It can change in any release without /// notice. pub type EncodedConfig = DoNotRelyOnWhatThisIs; #[cfg(feature = "formatting")] impl Iso8601 { /// 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); } } time-0.3.37/src/format_description/well_known/iso8601.rs000064400000000000000000000225061046102023000212010ustar 00000000000000//! The format described in ISO 8601. mod adt_hack; use core::num::NonZeroU8; #[doc(hidden, no_inline)] pub use self::adt_hack::DoNotRelyOnWhatThisIs; pub use self::adt_hack::EncodedConfig; /// The format described in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html). /// /// This implementation is of ISO 8601-1:2019. It may not be compatible with other versions. /// /// The const parameter `CONFIG` **must** be a value that was returned by [`Config::encode`]. /// Passing any other value is **unspecified behavior**. /// /// Example: 1997-11-21T09:55:06.000000000-06:00 /// /// # Examples #[cfg_attr(feature = "formatting", doc = "```rust")] #[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")] /// # use time::format_description::well_known::Iso8601; /// # use time_macros::datetime; /// assert_eq!( /// datetime!(1997-11-12 9:55:06 -6:00).format(&Iso8601::DEFAULT)?, /// "1997-11-12T09:55:06.000000000-06:00" /// ); /// # Ok::<_, time::Error>(()) /// ``` #[derive(Clone, Copy, PartialEq, Eq)] pub struct Iso8601; impl core::fmt::Debug for Iso8601 { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Iso8601") .field("config", &Config::decode(CONFIG)) .finish() } } /// Define associated constants for `Iso8601`. macro_rules! define_assoc_consts { ($($(#[$doc:meta])* $vis:vis const $const_name:ident = $format:expr;)*) => {$( const $const_name: EncodedConfig = $format.encode(); impl Iso8601<$const_name> { $(#[$doc])* $vis const $const_name: Self = Self; } )*}; } define_assoc_consts! { /// An [`Iso8601`] with the default configuration. /// /// The following is the default behavior: /// /// - The configuration can be used for both formatting and parsing. /// - The date, time, and UTC offset are all formatted. /// - Separators (such as `-` and `:`) are included. /// - The year contains four digits, such that the year must be between 0 and 9999. /// - The date uses the calendar format. /// - The time has precision to the second and nine decimal digits. /// - The UTC offset has precision to the minute. /// /// If you need different behavior, use another associated constant. For full customization, use /// [`Config::DEFAULT`] and [`Config`]'s methods to create a custom configuration. pub const DEFAULT = Config::DEFAULT; /// An [`Iso8601`] that can only be used for parsing. Using this to format a value is /// unspecified behavior. pub const PARSING = Config::PARSING; /// An [`Iso8601`] that handles only the date, but is otherwise the same as [`Config::DEFAULT`]. pub const DATE = Config::DEFAULT.set_formatted_components(FormattedComponents::Date); /// An [`Iso8601`] that handles only the time, but is otherwise the same as [`Config::DEFAULT`]. pub const TIME = Config::DEFAULT.set_formatted_components(FormattedComponents::Time); /// An [`Iso8601`] that handles only the UTC offset, but is otherwise the same as /// [`Config::DEFAULT`]. pub const OFFSET = Config::DEFAULT.set_formatted_components(FormattedComponents::Offset); /// An [`Iso8601`] that handles the date and time, but is otherwise the same as /// [`Config::DEFAULT`]. pub const DATE_TIME = Config::DEFAULT.set_formatted_components(FormattedComponents::DateTime); /// An [`Iso8601`] that handles the date, time, and UTC offset. This is the same as /// [`Config::DEFAULT`]. pub const DATE_TIME_OFFSET = Config::DEFAULT; /// An [`Iso8601`] that handles the time and UTC offset, but is otherwise the same as /// [`Config::DEFAULT`]. pub const TIME_OFFSET = Config::DEFAULT .set_formatted_components(FormattedComponents::TimeOffset); } /// Which components to format. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FormattedComponents { /// The configuration can only be used for parsing. Using this to format a value is /// unspecified behavior. None, /// Format only the date. Date, /// Format only the time. Time, /// Format only the UTC offset. Offset, /// Format the date and time. DateTime, /// Format the date, time, and UTC offset. DateTimeOffset, /// Format the time and UTC offset. TimeOffset, } /// Which format to use for the date. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DateKind { /// Use the year-month-day format. Calendar, /// Use the year-week-weekday format. Week, /// Use the week-ordinal format. Ordinal, } /// The precision and number of decimal digits present for the time. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TimePrecision { /// Format the hour only. Minutes, seconds, and nanoseconds will be represented with the /// specified number of decimal digits, if any. Hour { #[allow(missing_docs)] decimal_digits: Option, }, /// Format the hour and minute. Seconds and nanoseconds will be represented with the specified /// number of decimal digits, if any. Minute { #[allow(missing_docs)] decimal_digits: Option, }, /// Format the hour, minute, and second. Nanoseconds will be represented with the specified /// number of decimal digits, if any. Second { #[allow(missing_docs)] decimal_digits: Option, }, } /// 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, }; /// A configuration that can only be used for parsing. Using this to format a value is /// unspecified behavior. const PARSING: Self = Self { formatted_components: FormattedComponents::None, use_separators: false, year_is_six_digits: false, date_kind: DateKind::Calendar, time_precision: TimePrecision::Hour { decimal_digits: None, }, offset_precision: OffsetPrecision::Hour, }; /// Set whether the format the date, time, and/or UTC offset. pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self { Self { 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 } } } time-0.3.37/src/format_description/well_known/rfc2822.rs000064400000000000000000000020231046102023000211500ustar 00000000000000//! 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; time-0.3.37/src/format_description/well_known/rfc3339.rs000064400000000000000000000020141046102023000211540ustar 00000000000000//! 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; time-0.3.37/src/formatting/formattable.rs000064400000000000000000000264011046102023000164660ustar 00000000000000//! A trait that can be used to format an item from its components. use alloc::string::String; use alloc::vec::Vec; use core::ops::Deref; use std::io; use num_conv::prelude::*; use crate::format_description::well_known::iso8601::EncodedConfig; use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339}; use crate::format_description::{BorrowedFormatItem, OwnedFormatItem}; use crate::formatting::{ format_component, format_number_pad_zero, iso8601, write, MONTH_NAMES, WEEKDAY_NAMES, }; use crate::{error, Date, Time, UtcOffset}; /// A type that describes a format. /// /// Implementors of [`Formattable`] are [format descriptions](crate::format_description). /// /// [`Date::format`] and [`Time::format`] each use a format description to generate /// a String from their data. See the respective methods for usage examples. #[cfg_attr(docsrs, doc(notable_trait))] pub trait Formattable: sealed::Sealed {} impl Formattable for BorrowedFormatItem<'_> {} impl Formattable for [BorrowedFormatItem<'_>] {} impl Formattable for OwnedFormatItem {} impl Formattable for [OwnedFormatItem] {} impl Formattable for Rfc3339 {} impl Formattable for Rfc2822 {} impl Formattable for Iso8601 {} impl 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, time: Option