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