diff options
Diffstat (limited to 'src/date.rs')
-rw-r--r-- | src/date.rs | 92 |
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) + }) + } +} |