1 use crate::{
2     ext::LitIntExtension,
3     time_crate,
4     time_crate::{days_in_year, days_in_year_month, weeks_in_year},
5 };
6 use proc_macro2::TokenStream;
7 use quote::{quote, ToTokens};
8 use syn::{
9     parse::{Parse, ParseStream},
10     Ident, LitInt, Result, Token,
11 };
12 
13 pub(crate) struct Date {
14     year: i32,
15     ordinal: u16,
16 }
17 
18 impl Parse for Date {
parse(input: ParseStream<'_>) -> Result<Self>19     fn parse(input: ParseStream<'_>) -> Result<Self> {
20         let (year, year_span) = {
21             let year_sign = if input.peek(Token![+]) {
22                 input.parse::<Token![+]>()?;
23                 1
24             } else if input.peek(Token![-]) {
25                 input.parse::<Token![-]>()?;
26                 -1
27             } else {
28                 1
29             };
30             let year = input.parse::<LitInt>()?;
31             (year_sign * year.value::<i32>()?, year.span())
32         };
33         input.parse::<Token![-]>()?;
34 
35         // year-week-day
36         #[allow(clippy::manual_strip)]
37         let (year, ordinal) = if input.peek(Ident) {
38             let week = {
39                 let week = input.parse::<Ident>()?;
40                 let week_str = week.to_string();
41                 if week_str.starts_with('W') {
42                     LitInt::new(&week_str[1..], week.span())
43                 } else {
44                     return error!(week.span(), "expected week value to start with `W`");
45                 }
46             };
47             input.parse::<Token![-]>()?;
48             let day = input.parse::<LitInt>()?;
49 
50             week.ensure_in_range(0..=weeks_in_year(year) as isize)?;
51             day.ensure_in_range(1..=7)?;
52 
53             time_crate::Date::from_iso_ywd_unchecked(year, week.value()?, day.value()?).as_yo()
54         }
55         // year-month-day
56         else if input.peek2(Token![-]) {
57             let month = input.parse::<LitInt>()?;
58             input.parse::<Token![-]>()?;
59             let day = input.parse::<LitInt>()?;
60 
61             month.ensure_in_range(1..=12)?;
62             day.ensure_in_range(1..=days_in_year_month(year, month.value()?) as isize)?;
63 
64             time_crate::Date::from_ymd_unchecked(year, month.value()?, day.value()?).as_yo()
65         }
66         // year-ordinal
67         else {
68             let ordinal = input.parse::<LitInt>()?;
69             ordinal.ensure_in_range(1..=days_in_year(year) as isize)?;
70             (year, ordinal.value()?)
71         };
72 
73         LitInt::create(year)
74             .with_span(year_span)
75             .ensure_in_range(-100_000..=100_000)?;
76 
77         Ok(Self { year, ordinal })
78     }
79 }
80 
81 impl ToTokens for Date {
to_tokens(&self, tokens: &mut TokenStream)82     fn to_tokens(&self, tokens: &mut TokenStream) {
83         let Self { year, ordinal } = self;
84 
85         tokens.extend(quote! {
86             ::time::internals::Date::from_yo_unchecked(#year, #ordinal)
87         })
88     }
89 }
90