use std::iter::Peekable; use proc_macro::{token_stream, Span, TokenTree}; use crate::helpers::{consume_any_ident, consume_number, consume_punct}; use crate::to_tokens::ToTokenTree; use crate::Error; enum Period { Am, Pm, _24, } pub(crate) struct Time { pub(crate) hour: u8, pub(crate) minute: u8, pub(crate) second: u8, pub(crate) nanosecond: u32, } pub(crate) fn parse(chars: &mut Peekable) -> Result { fn consume_period(chars: &mut Peekable) -> (Option, Period) { if let Ok(span) = consume_any_ident(&["am", "AM"], chars) { (Some(span), Period::Am) } else if let Ok(span) = consume_any_ident(&["pm", "PM"], chars) { (Some(span), Period::Pm) } else { (None, Period::_24) } } let (hour_span, hour) = consume_number("hour", chars)?; let ((minute_span, minute), (second_span, second), (period_span, period)) = match consume_period(chars) { // Nothing but the 12-hour clock hour and AM/PM (period_span @ Some(_), period) => ( (Span::mixed_site(), 0), (Span::mixed_site(), 0.), (period_span, period), ), (None, _) => { consume_punct(':', chars)?; let (minute_span, minute) = consume_number::("minute", chars)?; let (second_span, second): (_, f64) = if consume_punct(':', chars).is_ok() { consume_number("second", chars)? } else { (Span::mixed_site(), 0.) }; let (period_span, period) = consume_period(chars); ( (minute_span, minute), (second_span, second), (period_span, period), ) } }; let hour = match (hour, period) { (0, Period::Am | Period::Pm) => { return Err(Error::InvalidComponent { name: "hour", value: hour.to_string(), span_start: Some(hour_span), span_end: Some(period_span.unwrap_or(hour_span)), }); } (12, Period::Am) => 0, (12, Period::Pm) => 12, (hour, Period::Am | Period::_24) => hour, (hour, Period::Pm) => hour + 12, }; if hour >= 24 { Err(Error::InvalidComponent { name: "hour", value: hour.to_string(), span_start: Some(hour_span), span_end: Some(period_span.unwrap_or(hour_span)), }) } else if minute >= 60 { Err(Error::InvalidComponent { name: "minute", value: minute.to_string(), span_start: Some(minute_span), span_end: Some(minute_span), }) } else if second >= 60. { Err(Error::InvalidComponent { name: "second", value: second.to_string(), span_start: Some(second_span), span_end: Some(second_span), }) } else { Ok(Time { hour, minute, second: second.trunc() as _, nanosecond: (second.fract() * 1_000_000_000.).round() as _, }) } } impl ToTokenTree for Time { fn into_token_tree(self) -> TokenTree { quote_group! {{ const TIME: ::time::Time = ::time::Time::__from_hms_nanos_unchecked( #(self.hour), #(self.minute), #(self.second), #(self.nanosecond), ); TIME }} } }