| 1 | use std::iter::Peekable; |
| 2 | |
| 3 | use num_conv::prelude::*; |
| 4 | use proc_macro::{token_stream, Span, TokenTree}; |
| 5 | use time_core::convert::*; |
| 6 | |
| 7 | use crate::helpers::{consume_any_ident, consume_number, consume_punct}; |
| 8 | use crate::to_tokens::ToTokenTree; |
| 9 | use crate::Error; |
| 10 | |
| 11 | pub(crate) struct Offset { |
| 12 | pub(crate) hours: i8, |
| 13 | pub(crate) minutes: i8, |
| 14 | pub(crate) seconds: i8, |
| 15 | } |
| 16 | |
| 17 | pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Offset, Error> { |
| 18 | if consume_any_ident(&["utc" , "UTC" ], chars).is_ok() { |
| 19 | return Ok(Offset { |
| 20 | hours: 0, |
| 21 | minutes: 0, |
| 22 | seconds: 0, |
| 23 | }); |
| 24 | } |
| 25 | |
| 26 | let sign = if consume_punct('+' , chars).is_ok() { |
| 27 | 1 |
| 28 | } else if consume_punct('-' , chars).is_ok() { |
| 29 | -1 |
| 30 | } else if let Some(tree) = chars.next() { |
| 31 | return Err(Error::UnexpectedToken { tree }); |
| 32 | } else { |
| 33 | return Err(Error::MissingComponent { |
| 34 | name: "sign" , |
| 35 | span_start: None, |
| 36 | span_end: None, |
| 37 | }); |
| 38 | }; |
| 39 | |
| 40 | let (hours_span, hours) = consume_number::<i8>("hour" , chars)?; |
| 41 | let (mut minutes_span, mut minutes) = (Span::mixed_site(), 0); |
| 42 | let (mut seconds_span, mut seconds) = (Span::mixed_site(), 0); |
| 43 | |
| 44 | if consume_punct(':' , chars).is_ok() { |
| 45 | let min = consume_number::<i8>("minute" , chars)?; |
| 46 | minutes_span = min.0; |
| 47 | minutes = min.1; |
| 48 | |
| 49 | if consume_punct(':' , chars).is_ok() { |
| 50 | let sec = consume_number::<i8>("second" , chars)?; |
| 51 | seconds_span = sec.0; |
| 52 | seconds = sec.1; |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | if hours > 25 { |
| 57 | Err(Error::InvalidComponent { |
| 58 | name: "hour" , |
| 59 | value: hours.to_string(), |
| 60 | span_start: Some(hours_span), |
| 61 | span_end: Some(hours_span), |
| 62 | }) |
| 63 | } else if minutes >= Minute::per(Hour).cast_signed() { |
| 64 | Err(Error::InvalidComponent { |
| 65 | name: "minute" , |
| 66 | value: minutes.to_string(), |
| 67 | span_start: Some(minutes_span), |
| 68 | span_end: Some(minutes_span), |
| 69 | }) |
| 70 | } else if seconds >= Second::per(Minute).cast_signed() { |
| 71 | Err(Error::InvalidComponent { |
| 72 | name: "second" , |
| 73 | value: seconds.to_string(), |
| 74 | span_start: Some(seconds_span), |
| 75 | span_end: Some(seconds_span), |
| 76 | }) |
| 77 | } else { |
| 78 | Ok(Offset { |
| 79 | hours: sign * hours, |
| 80 | minutes: sign * minutes, |
| 81 | seconds: sign * seconds, |
| 82 | }) |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | impl ToTokenTree for Offset { |
| 87 | fn into_token_tree(self) -> TokenTree { |
| 88 | quote_group! {{ |
| 89 | const OFFSET: ::time::UtcOffset = unsafe { |
| 90 | ::time::UtcOffset::__from_hms_unchecked( |
| 91 | #(self.hours), |
| 92 | #(self.minutes), |
| 93 | #(self.seconds), |
| 94 | ) |
| 95 | }; |
| 96 | OFFSET |
| 97 | }} |
| 98 | } |
| 99 | } |
| 100 | |