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