time_macros/
date.rs

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    // 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
128impl 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}