diff options
author | Chih-Hung Hsieh <chh@google.com> | 2020-10-21 01:19:48 -0700 |
---|---|---|
committer | Chih-Hung Hsieh <chh@google.com> | 2020-10-21 01:40:58 -0700 |
commit | ae890defb76e51f5026f2052f22ccf63a8977537 (patch) | |
tree | 927afed5091d7f8b5758d3dff9b2c29c4ff22446 | |
parent | 7ddf3ab2f0af49dc5e26c0f25d0a0bdfe1be8f9a (diff) | |
download | time-macros-impl-ae890defb76e51f5026f2052f22ccf63a8977537.tar.gz |
Import time-macros-impl-0.1.1
Bug: 169611678
Test: make
Change-Id: Iff0cba9b9681f211c42fffa7e44043353226fb93
-rw-r--r-- | .cargo_vcs_info.json | 5 | ||||
-rw-r--r-- | Cargo.toml | 43 | ||||
-rw-r--r-- | Cargo.toml.orig | 22 | ||||
l--------- | LICENSE | 1 | ||||
-rw-r--r-- | LICENSE-Apache | 202 | ||||
-rw-r--r-- | LICENSE-MIT | 19 | ||||
-rw-r--r-- | METADATA | 19 | ||||
-rw-r--r-- | MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | OWNERS | 1 | ||||
-rw-r--r-- | src/date.rs | 92 | ||||
-rw-r--r-- | src/ext.rs | 48 | ||||
-rw-r--r-- | src/lib.rs | 95 | ||||
-rw-r--r-- | src/offset.rs | 80 | ||||
-rw-r--r-- | src/time.rs | 133 | ||||
-rw-r--r-- | src/time_crate/date.rs | 147 | ||||
-rw-r--r-- | src/time_crate/mod.rs | 24 |
16 files changed, 931 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..347708d --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "0701287eb68bbf2ec1f3b8e8012d577ddaf90938" + } +} diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0d11564 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,43 @@ +# 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 believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "time-macros-impl" +version = "0.1.1" +authors = ["Jacob Pratt <the.z.cuber@gmail.com>"] +description = "Procedural macros for the time crate." +readme = "../README.md" +keywords = ["date", "time", "calendar", "duration"] +categories = ["date-and-time"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/time-rs/time" + +[lib] +proc-macro = true +[dependencies.proc-macro-hack] +version = "0.5" + +[dependencies.proc-macro2] +version = "1" + +[dependencies.quote] +version = "1" + +[dependencies.standback] +version = "0.2" +default-features = false + +[dependencies.syn] +version = "1" +features = ["proc-macro", "parsing", "printing", "derive"] +default-features = false diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..3ef79fc --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,22 @@ +[package] +name = "time-macros-impl" +version = "0.1.1" +authors = ["Jacob Pratt <the.z.cuber@gmail.com>"] +edition = "2018" +repository = "https://github.com/time-rs/time" +keywords = ["date", "time", "calendar", "duration"] +categories = ["date-and-time"] +readme = "../README.md" +license = "MIT OR Apache-2.0" +description = "Procedural macros for the time crate." + +[dependencies] +proc-macro2 = "1" +# I'm not entirely sure why `derive` is necessary here, but it fails without it. +syn = { version = "1", default-features = false, features = ["proc-macro", "parsing", "printing", "derive"] } +quote = "1" +proc-macro-hack = "0.5" +standback = { version = "0.2", default-features = false } + +[lib] +proc-macro = true @@ -0,0 +1 @@ +LICENSE-Apache
\ No newline at end of file diff --git a/LICENSE-Apache b/LICENSE-Apache new file mode 100644 index 0000000..3d2fb46 --- /dev/null +++ b/LICENSE-Apache @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Jacob Pratt + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..1ddffb9 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2019 Jacob Pratt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..32aebc6 --- /dev/null +++ b/METADATA @@ -0,0 +1,19 @@ +name: "time-macros-impl" +description: "Procedural macros for the time crate." +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/time-macros-impl" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/time-macros-impl/time-macros-impl-0.1.1.crate" + } + version: "0.1.1" + license_type: NOTICE + last_upgrade_date { + year: 2020 + month: 10 + day: 21 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 @@ -0,0 +1 @@ +include platform/prebuilts/rust:/OWNERS diff --git a/src/date.rs b/src/date.rs new file mode 100644 index 0000000..d246579 --- /dev/null +++ b/src/date.rs @@ -0,0 +1,92 @@ +use crate::{ + ext::LitIntExtension, + time_crate, + time_crate::{days_in_year, days_in_year_month, weeks_in_year}, +}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + Ident, LitInt, Result, Token, +}; + +pub(crate) struct Date { + year: i32, + ordinal: u16, +} + +impl Parse for Date { + fn parse(input: ParseStream<'_>) -> Result<Self> { + let (year, year_span) = { + let year_sign = if input.peek(Token![+]) { + input.parse::<Token![+]>()?; + 1 + } else if input.peek(Token![-]) { + input.parse::<Token![-]>()?; + -1 + } else { + 1 + }; + let year = input.parse::<LitInt>()?; + (year_sign * year.value::<i32>()?, year.span()) + }; + input.parse::<Token![-]>()?; + + // year-week-day + let (year, ordinal) = if input.peek(Ident) { + let week = { + let week = input.parse::<Ident>()?; + let week_str = week.to_string(); + if week_str.starts_with('W') { + LitInt::new(&week_str[1..], week.span()) + } else { + return error!(week.span(), "expected week value to start with `W`"); + } + }; + input.parse::<Token![-]>()?; + let day = input.parse::<LitInt>()?; + + week.ensure_in_range(0..=weeks_in_year(year) as isize)?; + day.ensure_in_range(1..=7)?; + + time_crate::Date::from_iso_ywd_unchecked(year, week.value()?, day.value()?).as_yo() + } + // year-month-day + else if input.peek2(Token![-]) { + let month = input.parse::<LitInt>()?; + input.parse::<Token![-]>()?; + let day = input.parse::<LitInt>()?; + + month.ensure_in_range(1..=12)?; + day.ensure_in_range(1..=days_in_year_month(year, month.value()?) as isize)?; + + time_crate::Date::from_ymd_unchecked(year, month.value()?, day.value()?).as_yo() + } + // year-ordinal + else { + let ordinal = input.parse::<LitInt>()?; + ordinal.ensure_in_range(1..=days_in_year(year) as isize)?; + (year, ordinal.value()?) + }; + + // TODO(upstream) Swap out the following when dtolnay/syn#748 is + // published on crates.io. Be sure to update Cargo.toml for the minimum + // version. + // LitInt::create(year).using_span(year_span).ensure_in_range(-100_000..=100_000)?; + if year < -100_000 || year > 100_000 { + return error!(year_span, "value must be in the range -100_000..=100_000"); + } + + Ok(Self { year, ordinal }) + } +} + +impl ToTokens for Date { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { year, ordinal } = self; + + tokens.extend(quote! { + ::time::internals::Date::from_yo_unchecked(#year, #ordinal) + }) + } +} diff --git a/src/ext.rs b/src/ext.rs new file mode 100644 index 0000000..abdf0b8 --- /dev/null +++ b/src/ext.rs @@ -0,0 +1,48 @@ +use proc_macro2::Span; +#[allow(unused_imports)] +use standback::prelude::*; +use std::{ + fmt::{Debug, Display}, + ops::RangeBounds, + str::FromStr, +}; +use syn::{LitInt, Result}; + +pub(crate) trait LitIntExtension { + fn create<T: Display>(value: T) -> Self; + fn with_span(self, span: Span) -> Self; + fn ensure_in_range(&self, range: impl RangeBounds<isize> + Debug) -> Result<()>; + fn value<T: FromStr + Display>(&self) -> Result<T> + where + T::Err: Display; +} + +impl LitIntExtension for LitInt { + fn create<T: Display>(value: T) -> Self { + Self::new(&value.to_string(), Span::call_site()) + } + + fn with_span(mut self, span: Span) -> Self { + self.set_span(span); + self + } + + #[allow(unstable_name_collisions)] + fn ensure_in_range(&self, range: impl RangeBounds<isize> + Debug) -> Result<()> { + if range.contains(&self.value()?) { + Ok(()) + } else { + error!(self.span(), "value must be in range {:?}", range) + } + } + + fn value<T: FromStr + Display>(&self) -> Result<T> + where + T::Err: Display, + { + match self.base10_parse() { + Ok(value) => Ok(value), + Err(e) => error!(self.span(), "{}", e), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ce3f4d2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,95 @@ +#![forbid(unsafe_code)] +#![deny( + anonymous_parameters, + rust_2018_idioms, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + const_err, + illegal_floating_point_literal_pattern, + late_bound_lifetime_arguments, + path_statements, + patterns_in_fns_without_body, + clippy::all +)] +#![warn( + unused_extern_crates, + missing_copy_implementations, + missing_debug_implementations, + single_use_lifetimes, + unused_qualifications, + variant_size_differences, + clippy::pedantic, + clippy::nursery, + clippy::decimal_literal_representation, + clippy::get_unwrap, + clippy::option_unwrap_used, + clippy::print_stdout, + clippy::result_unwrap_used +)] +#![allow( + clippy::inline_always, + clippy::cast_possible_wrap, + clippy::cast_lossless, + clippy::module_name_repetitions, + clippy::must_use_candidate, + clippy::use_self, // Not supported in some situations in older compilers. +)] + +// This is required on rustc < 1.42.0. +#[allow(unused_extern_crates)] +extern crate proc_macro; + +macro_rules! error { + ($message:literal) => { + error!(::proc_macro2::Span::call_site(), $message) + }; + + ($span:expr, $message:literal) => { + Err(::syn::Error::new($span, $message)) + }; + + ($span:expr, $($args:expr),+) => { + Err(::syn::Error::new($span, format!($($args),+))) + }; +} + +mod kw { + use syn::custom_keyword; + custom_keyword!(am); + custom_keyword!(pm); + custom_keyword!(AM); + custom_keyword!(PM); + custom_keyword!(utc); + custom_keyword!(UTC); +} + +mod date; +mod ext; +mod offset; +mod time; +mod time_crate; + +use date::Date; +use offset::Offset; +use proc_macro_hack::proc_macro_hack; +use quote::ToTokens; +use syn::parse_macro_input; +use time::Time; + +macro_rules! impl_macros { + ($($name:ident : $type:ty),* $(,)?) => { + $( + #[proc_macro_hack] + pub fn $name(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + parse_macro_input!(input as $type).to_token_stream().into() + } + )* + }; +} + +impl_macros! { + time: Time, + offset: Offset, + date: Date, +} diff --git a/src/offset.rs b/src/offset.rs new file mode 100644 index 0000000..86fe836 --- /dev/null +++ b/src/offset.rs @@ -0,0 +1,80 @@ +use crate::{ + ext::LitIntExtension, + kw::{utc, UTC}, +}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + LitInt, Result, Token, +}; + +#[repr(i8)] +enum Direction { + East = 1, + West = -1, +} + +pub(crate) struct Offset { + pub(crate) offset: i32, +} + +impl Parse for Offset { + fn parse(input: ParseStream<'_>) -> Result<Self> { + if input.peek(utc) { + input.parse::<utc>()?; + return Ok(Self { offset: 0 }); + } + if input.peek(UTC) { + input.parse::<UTC>()?; + return Ok(Self { offset: 0 }); + } + + let direction = if input.peek(Token![+]) { + input.parse::<Token![+]>()?; + Direction::East + } else if input.peek(Token![-]) { + input.parse::<Token![-]>()?; + Direction::West + } else { + return error!("offset must have an explicit sign"); + }; + + let hour = input.parse::<LitInt>()?; + + // Minutes are optional, defaulting to zero. + let minute = if input.peek(Token![:]) { + input.parse::<Token![:]>()?; + input.parse::<LitInt>()? + } else { + LitInt::create(0) + }; + + // Seconds are optional, defaulting to zero. + let second = if input.peek(Token![:]) { + input.parse::<Token![:]>()?; + input.parse::<LitInt>()? + } else { + LitInt::create(0) + }; + + // Ensure none of the components are out of range. + hour.ensure_in_range(0..24)?; + minute.ensure_in_range(0..60)?; + second.ensure_in_range(0..60)?; + + let offset = direction as i32 + * (hour.value::<i32>()? * 3_600 + + minute.value::<i32>()? * 60 + + second.value::<i32>()?); + + Ok(Self { offset }) + } +} + +impl ToTokens for Offset { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { offset } = self; + tokens.extend(quote! { ::time::UtcOffset::seconds(#offset) }); + } +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..b03f21d --- /dev/null +++ b/src/time.rs @@ -0,0 +1,133 @@ +use crate::ext::LitIntExtension; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + LitFloat, LitInt, Result, Token, +}; + +#[derive(PartialEq)] +enum AmPm { + Am, + Pm, +} + +impl Parse for AmPm { + fn parse(input: ParseStream<'_>) -> Result<Self> { + use crate::kw::{am, pm, AM, PM}; + if input.peek(am) { + input.parse::<am>()?; + Ok(AmPm::Am) + } else if input.peek(AM) { + input.parse::<AM>()?; + Ok(AmPm::Am) + } else if input.peek(pm) { + input.parse::<pm>()?; + Ok(AmPm::Pm) + } else if input.peek(PM) { + input.parse::<PM>()?; + Ok(AmPm::Pm) + } else { + error!("expected am or pm") + } + } +} + +pub(crate) struct Time { + pub(crate) hour: LitInt, + pub(crate) minute: LitInt, + pub(crate) second: LitInt, + pub(crate) nanosecond: LitInt, +} + +impl Parse for Time { + fn parse(input: ParseStream<'_>) -> Result<Self> { + let mut hour = input.parse::<LitInt>()?; + input.parse::<Token![:]>()?; + let minute = input.parse::<LitInt>()?; + + // Seconds and nanoseconds are optional, defaulting to zero. + let (second, nanosecond) = if input.peek(Token![:]) { + input.parse::<Token![:]>()?; + + if input.peek(LitFloat) { + let float = input.parse::<LitFloat>()?; + + // Temporary value to satisfy the compiler. + let float_str = float.to_string(); + let parts: Vec<_> = float_str.splitn(2, '.').collect(); + + let seconds = LitInt::new(parts[0], float.span()); + let nanoseconds = { + // Prepend a zero to avoid having an empty string. + // Strip the suffix to avoid syn thinking it's a float. + let raw_padded = format!("0{}", parts[1].trim_end_matches(float.suffix())); + + // Take an extra digit due to the padding. + let digits: String = raw_padded + .chars() + .filter(char::is_ascii_digit) + .take(10) + .collect(); + + // Scale the value based on how many digits were provided. + #[allow(clippy::cast_possible_truncation)] + let value = LitInt::new(&digits, float.span()).base10_parse::<usize>()? + * 10_usize.pow(10 - digits.len() as u32); + + LitInt::create(value).with_span(float.span()) + }; + + (seconds, nanoseconds) + } else { + (input.parse::<LitInt>()?, LitInt::create(0)) + } + } else { + (LitInt::create(0), LitInt::create(0)) + }; + + let am_pm = input.parse::<AmPm>().ok(); + + // Ensure none of the components are out of range. + match am_pm { + Some(am_pm) => { + hour.ensure_in_range(1..=12)?; + // Adjust the hour if necessary. + hour = match (hour.value()?, am_pm) { + (12, AmPm::Am) => LitInt::create(0).with_span(hour.span()), + (value, AmPm::Pm) if value != 12 => { + LitInt::create(value + 12).with_span(hour.span()) + } + _ => hour, + } + } + None => hour.ensure_in_range(0..24)?, + } + minute.ensure_in_range(0..60)?; + second.ensure_in_range(0..60)?; + // This likely isn't necessary, but it can't hurt. + nanosecond.ensure_in_range(0..1_000_000_000)?; + + Ok(Self { + hour, + minute, + second, + nanosecond, + }) + } +} + +impl ToTokens for Time { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + hour, + minute, + second, + nanosecond, + } = self; + + tokens.extend(quote! { + ::time::internals::Time::from_hms_nanos_unchecked(#hour, #minute, #second, #nanosecond) + }); + } +} diff --git a/src/time_crate/date.rs b/src/time_crate/date.rs new file mode 100644 index 0000000..981630e --- /dev/null +++ b/src/time_crate/date.rs @@ -0,0 +1,147 @@ +use super::Weekday::{self, Friday, Monday, Saturday, Sunday, Thursday, Tuesday, Wednesday}; +#[allow(unused_imports)] +use standback::prelude::*; + +fn is_leap_year(year: i32) -> bool { + (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) +} + +pub(crate) fn days_in_year(year: i32) -> u16 { + 365 + is_leap_year(year) as u16 +} + +/// The number of days in a month in both common and leap years. +const DAYS_IN_MONTH_COMMON_LEAP: [[u16; 12]; 2] = [ + [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], +]; + +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn days_in_year_month(year: i32, month: u8) -> u8 { + DAYS_IN_MONTH_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1] as u8 +} + +pub(crate) fn weeks_in_year(year: i32) -> u8 { + let weekday = Date { year, ordinal: 1 }.weekday(); + + if (weekday == Thursday) || (weekday == Wednesday && is_leap_year(year)) { + 53 + } else { + 52 + } +} + +pub(crate) struct Date { + year: i32, + pub(crate) ordinal: u16, +} + +impl Date { + pub(crate) const fn as_yo(&self) -> (i32, u16) { + (self.year, self.ordinal) + } + + pub(crate) fn month_day(&self) -> (u8, u8) { + 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; + + #[allow(clippy::cast_possible_truncation)] + { + if ordinal > days[10] { + (12, (ordinal - days[10]) as u8) + } else if ordinal > days[9] { + (11, (ordinal - days[9]) as u8) + } else if ordinal > days[8] { + (10, (ordinal - days[8]) as u8) + } else if ordinal > days[7] { + (9, (ordinal - days[7]) as u8) + } else if ordinal > days[6] { + (8, (ordinal - days[6]) as u8) + } else if ordinal > days[5] { + (7, (ordinal - days[5]) as u8) + } else if ordinal > days[4] { + (6, (ordinal - days[4]) as u8) + } else if ordinal > days[3] { + (5, (ordinal - days[3]) as u8) + } else if ordinal > days[2] { + (4, (ordinal - days[2]) as u8) + } else if ordinal > days[1] { + (3, (ordinal - days[1]) as u8) + } else if ordinal > days[0] { + (2, (ordinal - days[0]) as u8) + } else { + (1, ordinal as u8) + } + } + } + + #[allow(unstable_name_collisions)] + pub(crate) fn weekday(&self) -> Weekday { + let (month, day) = self.month_day(); + + let (month, adjusted_year) = if month < 3 { + (month + 12, self.year - 1) + } else { + (month, self.year) + }; + + match (day as i32 + (13 * (month as i32 + 1)) / 5 + adjusted_year + adjusted_year / 4 + - adjusted_year / 100 + + adjusted_year / 400) + .rem_euclid(7) + { + 0 => Saturday, + 1 => Sunday, + 2 => Monday, + 3 => Tuesday, + 4 => Wednesday, + 5 => Thursday, + 6 => Friday, + _ => unreachable!("A value mod 7 is always in the range 0..7"), + } + } + + pub(crate) fn from_iso_ywd_unchecked(year: i32, week: u8, iso_weekday_number: u8) -> Date { + let ordinal = week as u16 * 7 + iso_weekday_number as u16 + - (Self::from_yo_unchecked(year, 4) + .weekday() + .iso_weekday_number() as u16 + + 3); + + if ordinal < 1 { + return Self::from_yo_unchecked(year - 1, ordinal + days_in_year(year - 1)); + } + + let days_in_cur_year = days_in_year(year); + if ordinal > days_in_cur_year { + Self::from_yo_unchecked(year + 1, ordinal - days_in_cur_year) + } else { + Self::from_yo_unchecked(year, ordinal) + } + } + + pub(crate) fn from_ymd_unchecked(year: i32, month: u8, day: u8) -> Date { + /// 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], + ]; + + let ordinal = DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1]; + + Date { + year, + ordinal: ordinal + day as u16, + } + } + + pub(crate) const fn from_yo_unchecked(year: i32, ordinal: u16) -> Date { + Date { year, ordinal } + } +} diff --git a/src/time_crate/mod.rs b/src/time_crate/mod.rs new file mode 100644 index 0000000..a26cd11 --- /dev/null +++ b/src/time_crate/mod.rs @@ -0,0 +1,24 @@ +// Because time (indirectly) depends on this crate, we can't depend on time +// here. As such, we need to copy the functions over. If/when proc macros can be +// declared in the same crate, this will no longer be necessary. + +mod date; + +pub(crate) use date::{days_in_year, days_in_year_month, weeks_in_year, Date}; + +#[derive(PartialEq)] +pub(crate) enum Weekday { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, +} + +impl Weekday { + pub(crate) const fn iso_weekday_number(self) -> u8 { + self as u8 + 1 + } +} |