1use std::iter::Peekable;
2
3use num_conv::prelude::*;
4use proc_macro::{token_stream, Span, TokenTree};
5use time_core::convert::*;
6
7use crate::helpers::{consume_any_ident, consume_number, consume_punct};
8use crate::to_tokens::ToTokenTree;
9use crate::Error;
10
11pub(crate) struct Offset {
12 pub(crate) hours: i8,
13 pub(crate) minutes: i8,
14 pub(crate) seconds: i8,
15}
16
17pub(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
86impl 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}