1use std::iter::Peekable;
2
3use proc_macro::{token_stream, Span, TokenTree};
4use time_core::convert::*;
5
6use crate::helpers::{consume_any_ident, consume_number, consume_punct};
7use crate::to_tokens::ToTokenTree;
8use crate::Error;
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
23pub(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 (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
107impl 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}