Skip to main content

time_macros/
time.rs

1use std::iter::{self, Peekable};
2
3use num_conv::prelude::*;
4use proc_macro::{Span, TokenStream, TokenTree, token_stream};
5use time_core::unit::*;
6
7use crate::Error;
8use crate::helpers::{consume_any_ident, consume_number, consume_punct};
9use crate::to_tokens::ToTokenStream;
10
11enum Period {
12    Am,
13    Pm,
14    _24,
15}
16
17/// A parsed time.
18///
19/// The hour must be in `0..24`, the minute and second in `0..60`, and the nanosecond in
20/// `0..1_000_000_000`. These ranges are required by the unchecked `Time` constructor used when
21/// emitting this value.
22pub(crate) struct Time {
23    pub(crate) hour: u8,
24    pub(crate) minute: u8,
25    pub(crate) second: u8,
26    pub(crate) nanosecond: u32,
27}
28
29fn parse_second_and_nanosecond(
30    chars: &mut Peekable<token_stream::IntoIter>,
31) -> Result<(Span, u8, u32), Error> {
32    match chars.next() {
33        Some(TokenTree::Literal(literal)) => {
34            let span = literal.span();
35            let raw = literal.to_string().replace('_', "");
36
37            if let Some((second, subsecond)) = raw.split_once('.') {
38                let Ok(second) = second.parse() else {
39                    return Err(Error::InvalidComponent {
40                        name: "second",
41                        value: raw,
42                        span_start: Some(span.start()),
43                        span_end: Some(span.end()),
44                    });
45                };
46
47                let subsecond = subsecond
48                    .chars()
49                    .chain(iter::repeat('0'))
50                    .take(9)
51                    .collect::<String>();
52                let Ok(nanosecond) = subsecond.parse() else {
53                    return Err(Error::InvalidComponent {
54                        name: "second",
55                        value: raw,
56                        span_start: Some(span.start()),
57                        span_end: Some(span.end()),
58                    });
59                };
60
61                Ok((span, second, nanosecond))
62            } else {
63                let Ok(second) = raw.parse() else {
64                    return Err(Error::InvalidComponent {
65                        name: "second",
66                        value: raw,
67                        span_start: Some(span.start()),
68                        span_end: Some(span.end()),
69                    });
70                };
71                Ok((span, second, 0))
72            }
73        }
74        Some(tree) => Err(Error::UnexpectedToken { tree }),
75        None => Err(Error::UnexpectedEndOfInput),
76    }
77}
78
79pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Time, Error> {
80    fn consume_period(chars: &mut Peekable<token_stream::IntoIter>) -> (Option<Span>, Period) {
81        if let Ok(span) = consume_any_ident(&["am", "AM"], chars) {
82            (Some(span), Period::Am)
83        } else if let Ok(span) = consume_any_ident(&["pm", "PM"], chars) {
84            (Some(span), Period::Pm)
85        } else {
86            (None, Period::_24)
87        }
88    }
89
90    let (hour_span, hour) = consume_number("hour", chars)?;
91
92    let ((minute_span, minute), (second_span, second, nanosecond), (period_span, period)) =
93        match consume_period(chars) {
94            // Nothing but the 12-hour clock hour and AM/PM
95            (period_span @ Some(_), period) => (
96                (Span::mixed_site(), 0),
97                (Span::mixed_site(), 0, 0),
98                (period_span, period),
99            ),
100            (None, _) => {
101                consume_punct(':', chars)?;
102                let (minute_span, minute) = consume_number::<u8>("minute", chars)?;
103                let (second_span, second, nanosecond) = if consume_punct(':', chars).is_ok() {
104                    parse_second_and_nanosecond(chars)?
105                } else {
106                    (Span::mixed_site(), 0, 0)
107                };
108                let (period_span, period) = consume_period(chars);
109                (
110                    (minute_span, minute),
111                    (second_span, second, nanosecond),
112                    (period_span, period),
113                )
114            }
115        };
116
117    let hour: u16 = match (hour, period) {
118        (0, Period::Am | Period::Pm) => {
119            return Err(Error::InvalidComponent {
120                name: "hour",
121                value: hour.to_string(),
122                span_start: Some(hour_span.start()),
123                span_end: Some(period_span.unwrap_or_else(|| hour_span.end())),
124            });
125        }
126        (12, Period::Am) => 0,
127        (12, Period::Pm) => 12,
128        (hour, Period::Am | Period::_24) => hour,
129        (hour, Period::Pm) => hour + 12,
130    };
131
132    if hour >= Hour::per_t(Day) {
133        Err(Error::InvalidComponent {
134            name: "hour",
135            value: hour.to_string(),
136            span_start: Some(hour_span.start()),
137            span_end: Some(period_span.unwrap_or_else(|| hour_span.end())),
138        })
139    } else if minute >= Minute::per_t(Hour) {
140        Err(Error::InvalidComponent {
141            name: "minute",
142            value: minute.to_string(),
143            span_start: Some(minute_span.start()),
144            span_end: Some(minute_span.end()),
145        })
146    } else if second >= Second::per_t(Minute) {
147        Err(Error::InvalidComponent {
148            name: "second",
149            value: second.to_string(),
150            span_start: Some(second_span.start()),
151            span_end: Some(second_span.end()),
152        })
153    } else {
154        Ok(Time {
155            hour: hour.truncate(), // checked to be in range
156            minute,
157            second,
158            nanosecond,
159        })
160    }
161}
162
163impl ToTokenStream for Time {
164    fn into_token_stream(self) -> TokenStream {
165        // Safety: The parser checks all four components before constructing `Time`.
166        quote! {
167            unsafe {
168                ::time::Time::__from_hms_nanos_unchecked(
169                    #(self.hour),
170                    #(self.minute),
171                    #(self.second),
172                    #(self.nanosecond),
173                )
174            }
175        }
176    }
177}