Skip to main content

time_macros/
time.rs

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