summaryrefslogtreecommitdiff
path: root/src/date.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/date.rs')
-rw-r--r--src/date.rs92
1 files changed, 92 insertions, 0 deletions
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)
+ })
+ }
+}