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 (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}