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