1use std::iter::Peekable;
2
3use num_conv::Truncate;
4use proc_macro::{token_stream, TokenTree};
5use time_core::util::{days_in_year, weeks_in_year};
6
7use crate::helpers::{
8 consume_any_ident, consume_number, consume_punct, days_in_year_month, ymd_to_yo, ywd_to_yo,
9};
10use crate::to_tokens::ToTokenTree;
11use crate::Error;
12
13#[cfg(feature = "large-dates")]
14const MAX_YEAR: i32 = 999_999;
15#[cfg(not(feature = "large-dates"))]
16const 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(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 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 let (month_or_ordinal_span, month_or_ordinal) =
81 consume_number::<u16>("month or ordinal", chars)?;
82
83 if consume_punct('-', chars).is_ok() {
85 let (month_span, month) = (month_or_ordinal_span, month_or_ordinal);
86 let (day_span, day) = consume_number::<u8>("day", chars)?;
87
88 if month == 0 || month > 12 {
89 return Err(Error::InvalidComponent {
90 name: "month",
91 value: month.to_string(),
92 span_start: Some(month_span),
93 span_end: Some(month_span),
94 });
95 }
96 let month = month.truncate();
97 if day == 0 || day > days_in_year_month(year, month) {
98 return Err(Error::InvalidComponent {
99 name: "day",
100 value: day.to_string(),
101 span_start: Some(day_span),
102 span_end: Some(day_span),
103 });
104 }
105
106 let (year, ordinal) = ymd_to_yo(year, month, day);
107
108 Ok(Date { year, ordinal })
109 }
110 else {
112 let (ordinal_span, ordinal) = (month_or_ordinal_span, month_or_ordinal);
113
114 if ordinal == 0 || ordinal > days_in_year(year) {
115 return Err(Error::InvalidComponent {
116 name: "ordinal",
117 value: ordinal.to_string(),
118 span_start: Some(ordinal_span),
119 span_end: Some(ordinal_span),
120 });
121 }
122
123 Ok(Date { year, ordinal })
124 }
125}
126
127impl ToTokenTree for Date {
128 fn into_token_tree(self) -> TokenTree {
129 quote_group! {{
130 const DATE: ::time::Date = unsafe {
131 ::time::Date::__from_ordinal_date_unchecked(
132 #(self.year),
133 #(self.ordinal),
134 )
135 };
136 DATE
137 }}
138 }
139}