1 | use std::iter::Peekable; |
2 | |
3 | use proc_macro::{token_stream, Span, TokenTree}; |
4 | use time_core::convert::*; |
5 | |
6 | use crate::helpers::{consume_any_ident, consume_number, consume_punct}; |
7 | use crate::to_tokens::ToTokenTree; |
8 | use crate::Error; |
9 | |
10 | enum Period { |
11 | Am, |
12 | Pm, |
13 | _24, |
14 | } |
15 | |
16 | pub(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 | |
23 | pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Time, Error> { |
24 | fn consume_period(chars: &mut Peekable<token_stream::IntoIter>) -> (Option<Span>, Period) { |
25 | if let Ok(span) = consume_any_ident(&["am" , "AM" ], chars) { |
26 | (Some(span), Period::Am) |
27 | } else if let Ok(span) = consume_any_ident(&["pm" , "PM" ], chars) { |
28 | (Some(span), Period::Pm) |
29 | } else { |
30 | (None, Period::_24) |
31 | } |
32 | } |
33 | |
34 | let (hour_span, hour) = consume_number("hour" , chars)?; |
35 | |
36 | let ((minute_span, minute), (second_span, second), (period_span, period)) = |
37 | match consume_period(chars) { |
38 | // Nothing but the 12-hour clock hour and AM/PM |
39 | (period_span @ Some(_), period) => ( |
40 | (Span::mixed_site(), 0), |
41 | (Span::mixed_site(), 0.), |
42 | (period_span, period), |
43 | ), |
44 | (None, _) => { |
45 | consume_punct(':' , chars)?; |
46 | let (minute_span, minute) = consume_number::<u8>("minute" , chars)?; |
47 | let (second_span, second): (_, f64) = if consume_punct(':' , chars).is_ok() { |
48 | consume_number("second" , chars)? |
49 | } else { |
50 | (Span::mixed_site(), 0.) |
51 | }; |
52 | let (period_span, period) = consume_period(chars); |
53 | ( |
54 | (minute_span, minute), |
55 | (second_span, second), |
56 | (period_span, period), |
57 | ) |
58 | } |
59 | }; |
60 | |
61 | let hour = match (hour, period) { |
62 | (0, Period::Am | Period::Pm) => { |
63 | return Err(Error::InvalidComponent { |
64 | name: "hour" , |
65 | value: hour.to_string(), |
66 | span_start: Some(hour_span), |
67 | span_end: Some(period_span.unwrap_or(hour_span)), |
68 | }); |
69 | } |
70 | (12, Period::Am) => 0, |
71 | (12, Period::Pm) => 12, |
72 | (hour, Period::Am | Period::_24) => hour, |
73 | (hour, Period::Pm) => hour + 12, |
74 | }; |
75 | |
76 | if hour >= Hour::per(Day) { |
77 | Err(Error::InvalidComponent { |
78 | name: "hour" , |
79 | value: hour.to_string(), |
80 | span_start: Some(hour_span), |
81 | span_end: Some(period_span.unwrap_or(hour_span)), |
82 | }) |
83 | } else if minute >= Minute::per(Hour) { |
84 | Err(Error::InvalidComponent { |
85 | name: "minute" , |
86 | value: minute.to_string(), |
87 | span_start: Some(minute_span), |
88 | span_end: Some(minute_span), |
89 | }) |
90 | } else if second >= Second::per(Minute) as _ { |
91 | Err(Error::InvalidComponent { |
92 | name: "second" , |
93 | value: second.to_string(), |
94 | span_start: Some(second_span), |
95 | span_end: Some(second_span), |
96 | }) |
97 | } else { |
98 | Ok(Time { |
99 | hour, |
100 | minute, |
101 | second: second.trunc() as _, |
102 | nanosecond: (second.fract() * Nanosecond::per(Second) as f64).round() as _, |
103 | }) |
104 | } |
105 | } |
106 | |
107 | impl ToTokenTree for Time { |
108 | fn into_token_tree(self) -> TokenTree { |
109 | quote_group! {{ |
110 | const TIME: ::time::Time = unsafe { |
111 | ::time::Time::__from_hms_nanos_unchecked( |
112 | #(self.hour), |
113 | #(self.minute), |
114 | #(self.second), |
115 | #(self.nanosecond), |
116 | ) |
117 | }; |
118 | TIME |
119 | }} |
120 | } |
121 | } |
122 | |