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