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 {
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 (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(), minute,
157 second,
158 nanosecond,
159 })
160 }
161}
162
163impl ToTokenStream for Time {
164 fn into_token_stream(self) -> TokenStream {
165 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}