| 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 | |