Skip to main content

time_macros/
date.rs

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    // year-week-day
50    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()); // consume 'W' and possibly the week number
56
57        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    // We don't yet know whether it's year-month-day or year-ordinal.
104    let (month_or_ordinal_span, month_or_ordinal) =
105        consume_number::<u16>("month or ordinal", chars)?;
106
107    // year-month-day
108    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    // year-ordinal
135    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}