1use std::iter::Peekable;
2
3use num_conv::Truncate;
4use proc_macro::token_stream;
5use time_core::util::{days_in_year, weeks_in_year};
6
7use crate::Error;
8use crate::helpers::{consume_number, consume_punct, days_in_year_month, ymd_to_yo, ywd_to_yo};
9use crate::to_tokens::ToTokenStream;
10
11pub(crate) const MIN_YEAR: i32 = -MAX_YEAR;
12
13#[cfg(feature = "large-dates")]
14pub(crate) const MAX_YEAR: i32 = 999_999;
15#[cfg(not(feature = "large-dates"))]
16pub(crate) const MAX_YEAR: i32 = 9_999;
17
18pub(crate) struct Date {
23 pub(crate) year: i32,
24 pub(crate) ordinal: u16,
25}
26
27pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Date, Error> {
28 let (year_sign_span, year_sign, explicit_sign) = if let Ok(span) = consume_punct('-', chars) {
29 (Some(span), -1, true)
30 } else if let Ok(span) = consume_punct('+', chars) {
31 (Some(span), 1, true)
32 } else {
33 (None, 1, false)
34 };
35 let (year_span, mut year) = consume_number::<i32>("year", chars)?;
36 year *= year_sign;
37 if year.abs() > MAX_YEAR {
38 return Err(Error::InvalidComponent {
39 name: "year",
40 value: year.to_string(),
41 span_start: Some(year_sign_span.unwrap_or_else(|| year_span.start())),
42 span_end: Some(year_span.end()),
43 });
44 }
45 if !explicit_sign && year.abs() >= 10_000 {
46 return Err(Error::Custom {
47 message: "years with more than four digits must have an explicit sign".into(),
48 span_start: Some(year_sign_span.unwrap_or_else(|| year_span.start())),
49 span_end: Some(year_span.end()),
50 });
51 }
52
53 consume_punct('-', chars)?;
54
55 if let Some(proc_macro::TokenTree::Ident(ident)) = chars.peek()
57 && let s = ident.to_string()
58 && s.starts_with('W')
59 {
60 let w_span = ident.span();
61 drop(chars.next()); let (week_span, week, day_span, day);
64
65 if s.len() == 1 {
66 (week_span, week) = consume_number::<u8>("week", chars)?;
67 consume_punct('-', chars)?;
68 (day_span, day) = consume_number::<u8>("day", chars)?;
69 } else {
70 let presumptive_week = &s[1..];
71 if presumptive_week.bytes().all(|d| d.is_ascii_digit())
72 && let Ok(week_number) = presumptive_week.replace('_', "").parse()
73 {
74 (week_span, week) = (w_span, week_number);
75 consume_punct('-', chars)?;
76 (day_span, day) = consume_number::<u8>("day", chars)?;
77 } else {
78 return Err(Error::InvalidComponent {
79 name: "week",
80 value: presumptive_week.to_string(),
81 span_start: Some(w_span.start()),
82 span_end: Some(w_span.end()),
83 });
84 }
85 };
86
87 if week > weeks_in_year(year) {
88 return Err(Error::InvalidComponent {
89 name: "week",
90 value: week.to_string(),
91 span_start: Some(w_span.start()),
92 span_end: Some(week_span.end()),
93 });
94 }
95 if day == 0 || day > 7 {
96 return Err(Error::InvalidComponent {
97 name: "day",
98 value: day.to_string(),
99 span_start: Some(day_span.start()),
100 span_end: Some(day_span.end()),
101 });
102 }
103
104 let (year, ordinal) = ywd_to_yo(year, week, day, day_span)?;
105
106 return Ok(Date { year, ordinal });
107 }
108
109 let (month_or_ordinal_span, month_or_ordinal) =
111 consume_number::<u16>("month or ordinal", chars)?;
112
113 if consume_punct('-', chars).is_ok() {
115 let (month_span, month) = (month_or_ordinal_span, month_or_ordinal);
116 let (day_span, day) = consume_number::<u8>("day", chars)?;
117
118 if month == 0 || month > 12 {
119 return Err(Error::InvalidComponent {
120 name: "month",
121 value: month.to_string(),
122 span_start: Some(month_span.start()),
123 span_end: Some(month_span.end()),
124 });
125 }
126 let month = month.truncate();
127 if day == 0 || day > days_in_year_month(year, month) {
128 return Err(Error::InvalidComponent {
129 name: "day",
130 value: day.to_string(),
131 span_start: Some(day_span.start()),
132 span_end: Some(day_span.end()),
133 });
134 }
135
136 let (year, ordinal) = ymd_to_yo(year, month, day);
137
138 Ok(Date { year, ordinal })
139 }
140 else {
142 let (ordinal_span, ordinal) = (month_or_ordinal_span, month_or_ordinal);
143
144 if ordinal == 0 || ordinal > days_in_year(year) {
145 return Err(Error::InvalidComponent {
146 name: "ordinal",
147 value: ordinal.to_string(),
148 span_start: Some(ordinal_span.start()),
149 span_end: Some(ordinal_span.end()),
150 });
151 }
152
153 Ok(Date { year, ordinal })
154 }
155}
156
157impl ToTokenStream for Date {
158 fn into_token_stream(self) -> proc_macro::TokenStream {
159 quote! {
162 unsafe {
163 ::time::Date::__from_ordinal_date_unchecked(
164 #(self.year),
165 #(self.ordinal),
166 )
167 }
168 }
169 }
170}