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