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 {
19 pub(crate) year: i32,
20 pub(crate) ordinal: u16,
21}
22
23pub(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_else(|| year_span.start())),
38 span_end: Some(year_span.end()),
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_else(|| year_span.start())),
45 span_end: Some(year_span.end()),
46 });
47 }
48
49 consume_punct('-', chars)?;
50
51 if let Some(proc_macro::TokenTree::Ident(ident)) = chars.peek()
53 && let s = ident.to_string()
54 && s.starts_with('W')
55 {
56 let w_span = ident.span();
57 drop(chars.next()); let (week_span, week, day_span, day);
60
61 if s.len() == 1 {
62 (week_span, week) = consume_number::<u8>("week", chars)?;
63 consume_punct('-', chars)?;
64 (day_span, day) = consume_number::<u8>("day", chars)?;
65 } else {
66 let presumptive_week = &s[1..];
67 if presumptive_week.bytes().all(|d| d.is_ascii_digit())
68 && let Ok(week_number) = presumptive_week.replace('_', "").parse()
69 {
70 (week_span, week) = (w_span, week_number);
71 consume_punct('-', chars)?;
72 (day_span, day) = consume_number::<u8>("day", chars)?;
73 } else {
74 return Err(Error::InvalidComponent {
75 name: "week",
76 value: presumptive_week.to_string(),
77 span_start: Some(w_span.start()),
78 span_end: Some(w_span.end()),
79 });
80 }
81 };
82
83 if week > weeks_in_year(year) {
84 return Err(Error::InvalidComponent {
85 name: "week",
86 value: week.to_string(),
87 span_start: Some(w_span.start()),
88 span_end: Some(week_span.end()),
89 });
90 }
91 if day == 0 || day > 7 {
92 return Err(Error::InvalidComponent {
93 name: "day",
94 value: day.to_string(),
95 span_start: Some(day_span.start()),
96 span_end: Some(day_span.end()),
97 });
98 }
99
100 let (year, ordinal) = ywd_to_yo(year, week, day);
101
102 return Ok(Date { year, ordinal });
103 }
104
105 let (month_or_ordinal_span, month_or_ordinal) =
107 consume_number::<u16>("month or ordinal", chars)?;
108
109 if consume_punct('-', chars).is_ok() {
111 let (month_span, month) = (month_or_ordinal_span, month_or_ordinal);
112 let (day_span, day) = consume_number::<u8>("day", chars)?;
113
114 if month == 0 || month > 12 {
115 return Err(Error::InvalidComponent {
116 name: "month",
117 value: month.to_string(),
118 span_start: Some(month_span.start()),
119 span_end: Some(month_span.end()),
120 });
121 }
122 let month = month.truncate();
123 if day == 0 || day > days_in_year_month(year, month) {
124 return Err(Error::InvalidComponent {
125 name: "day",
126 value: day.to_string(),
127 span_start: Some(day_span.start()),
128 span_end: Some(day_span.end()),
129 });
130 }
131
132 let (year, ordinal) = ymd_to_yo(year, month, day);
133
134 Ok(Date { year, ordinal })
135 }
136 else {
138 let (ordinal_span, ordinal) = (month_or_ordinal_span, month_or_ordinal);
139
140 if ordinal == 0 || ordinal > days_in_year(year) {
141 return Err(Error::InvalidComponent {
142 name: "ordinal",
143 value: ordinal.to_string(),
144 span_start: Some(ordinal_span.start()),
145 span_end: Some(ordinal_span.end()),
146 });
147 }
148
149 Ok(Date { year, ordinal })
150 }
151}
152
153impl ToTokenStream for Date {
154 fn append_to(self, ts: &mut proc_macro::TokenStream) {
155 quote_append! { ts
156 unsafe {
157 ::time::Date::__from_ordinal_date_unchecked(
158 #(self.year),
159 #(self.ordinal),
160 )
161 }
162 }
163 }
164}