1 | use std::iter::Peekable; |
2 | |
3 | use num_conv::Truncate; |
4 | use proc_macro::{token_stream, TokenTree}; |
5 | use time_core::util::{days_in_year, weeks_in_year}; |
6 | |
7 | use crate::helpers::{ |
8 | consume_any_ident, consume_number, consume_punct, days_in_year_month, ymd_to_yo, ywd_to_yo, |
9 | }; |
10 | use crate::to_tokens::ToTokenTree; |
11 | use crate::Error; |
12 | |
13 | #[cfg (feature = "large-dates" )] |
14 | const MAX_YEAR: i32 = 999_999; |
15 | #[cfg (not(feature = "large-dates" ))] |
16 | const MAX_YEAR: i32 = 9_999; |
17 | |
18 | pub(crate) struct Date { |
19 | pub(crate) year: i32, |
20 | pub(crate) ordinal: u16, |
21 | } |
22 | |
23 | pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Date, Error> { |
24 | let (year_sign_span, year_sign, explicit_sign) = if let Ok(span) = consume_punct('-' , chars) { |
25 | (Some(span), -1, true) |
26 | } else if let Ok(span) = consume_punct('+' , chars) { |
27 | (Some(span), 1, true) |
28 | } else { |
29 | (None, 1, false) |
30 | }; |
31 | let (year_span, mut year) = consume_number::<i32>("year" , chars)?; |
32 | year *= year_sign; |
33 | if year.abs() > MAX_YEAR { |
34 | return Err(Error::InvalidComponent { |
35 | name: "year" , |
36 | value: year.to_string(), |
37 | span_start: Some(year_sign_span.unwrap_or(year_span)), |
38 | span_end: Some(year_span), |
39 | }); |
40 | } |
41 | if !explicit_sign && year.abs() >= 10_000 { |
42 | return Err(Error::Custom { |
43 | message: "years with more than four digits must have an explicit sign" .into(), |
44 | span_start: Some(year_sign_span.unwrap_or(year_span)), |
45 | span_end: Some(year_span), |
46 | }); |
47 | } |
48 | |
49 | consume_punct('-' , chars)?; |
50 | |
51 | // year-week-day |
52 | if let Ok(w_span) = consume_any_ident(&["W" ], chars) { |
53 | let (week_span, week) = consume_number::<u8>("week" , chars)?; |
54 | consume_punct('-' , chars)?; |
55 | let (day_span, day) = consume_number::<u8>("day" , chars)?; |
56 | |
57 | if week > weeks_in_year(year) { |
58 | return Err(Error::InvalidComponent { |
59 | name: "week" , |
60 | value: week.to_string(), |
61 | span_start: Some(w_span), |
62 | span_end: Some(week_span), |
63 | }); |
64 | } |
65 | if day == 0 || day > 7 { |
66 | return Err(Error::InvalidComponent { |
67 | name: "day" , |
68 | value: day.to_string(), |
69 | span_start: Some(day_span), |
70 | span_end: Some(day_span), |
71 | }); |
72 | } |
73 | |
74 | let (year, ordinal) = ywd_to_yo(year, week, day); |
75 | |
76 | return Ok(Date { year, ordinal }); |
77 | } |
78 | |
79 | // We don't yet know whether it's year-month-day or year-ordinal. |
80 | let (month_or_ordinal_span, month_or_ordinal) = |
81 | consume_number::<u16>("month or ordinal" , chars)?; |
82 | |
83 | // year-month-day |
84 | #[allow (clippy::branches_sharing_code)] // clarity |
85 | if consume_punct('-' , chars).is_ok() { |
86 | let (month_span, month) = (month_or_ordinal_span, month_or_ordinal); |
87 | let (day_span, day) = consume_number::<u8>("day" , chars)?; |
88 | |
89 | if month == 0 || month > 12 { |
90 | return Err(Error::InvalidComponent { |
91 | name: "month" , |
92 | value: month.to_string(), |
93 | span_start: Some(month_span), |
94 | span_end: Some(month_span), |
95 | }); |
96 | } |
97 | let month = month.truncate(); |
98 | if day == 0 || day > days_in_year_month(year, month) { |
99 | return Err(Error::InvalidComponent { |
100 | name: "day" , |
101 | value: day.to_string(), |
102 | span_start: Some(day_span), |
103 | span_end: Some(day_span), |
104 | }); |
105 | } |
106 | |
107 | let (year, ordinal) = ymd_to_yo(year, month, day); |
108 | |
109 | Ok(Date { year, ordinal }) |
110 | } |
111 | // year-ordinal |
112 | else { |
113 | let (ordinal_span, ordinal) = (month_or_ordinal_span, month_or_ordinal); |
114 | |
115 | if ordinal == 0 || ordinal > days_in_year(year) { |
116 | return Err(Error::InvalidComponent { |
117 | name: "ordinal" , |
118 | value: ordinal.to_string(), |
119 | span_start: Some(ordinal_span), |
120 | span_end: Some(ordinal_span), |
121 | }); |
122 | } |
123 | |
124 | Ok(Date { year, ordinal }) |
125 | } |
126 | } |
127 | |
128 | impl ToTokenTree for Date { |
129 | fn into_token_tree(self) -> TokenTree { |
130 | quote_group! {{ |
131 | const DATE: ::time::Date = unsafe { |
132 | ::time::Date::__from_ordinal_date_unchecked( |
133 | #(self.year), |
134 | #(self.ordinal), |
135 | ) |
136 | }; |
137 | DATE |
138 | }} |
139 | } |
140 | } |
141 | |