Skip to main content

time_macros/
offset.rs

1use std::iter::Peekable;
2
3use proc_macro::{Span, TokenStream, token_stream};
4use time_core::unit::*;
5
6use crate::Error;
7use crate::helpers::{consume_any_ident, consume_number, consume_punct};
8use crate::to_tokens::ToTokenStream;
9
10/// A parsed UTC offset.
11///
12/// The hours must be in `-25..=25`, and the minutes and seconds in `-59..=59`. These ranges are
13/// required by the unchecked `UtcOffset` constructor used when emitting this value. All nonzero
14/// components also have the same sign, as required for a valid offset.
15pub(crate) struct Offset {
16    pub(crate) hours: i8,
17    pub(crate) minutes: i8,
18    pub(crate) seconds: i8,
19}
20
21pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Offset, Error> {
22    if consume_any_ident(&["utc", "UTC"], chars).is_ok() {
23        return Ok(Offset {
24            hours: 0,
25            minutes: 0,
26            seconds: 0,
27        });
28    }
29
30    let sign = if consume_punct('+', chars).is_ok() {
31        1
32    } else if consume_punct('-', chars).is_ok() {
33        -1
34    } else if let Some(tree) = chars.next() {
35        return Err(Error::UnexpectedToken { tree });
36    } else {
37        return Err(Error::MissingComponent {
38            name: "sign",
39            span_start: None,
40            span_end: None,
41        });
42    };
43
44    let (hours_span, hours) = consume_number::<i8>("hour", chars)?;
45    let (mut minutes_span, mut minutes) = (Span::mixed_site(), 0);
46    let (mut seconds_span, mut seconds) = (Span::mixed_site(), 0);
47
48    if consume_punct(':', chars).is_ok() {
49        let min = consume_number::<i8>("minute", chars)?;
50        minutes_span = min.0;
51        minutes = min.1;
52
53        if consume_punct(':', chars).is_ok() {
54            let sec = consume_number::<i8>("second", chars)?;
55            seconds_span = sec.0;
56            seconds = sec.1;
57        }
58    }
59
60    if hours > 25 {
61        Err(Error::InvalidComponent {
62            name: "hour",
63            value: hours.to_string(),
64            span_start: Some(hours_span.start()),
65            span_end: Some(hours_span.end()),
66        })
67    } else if minutes >= Minute::per_t(Hour) {
68        Err(Error::InvalidComponent {
69            name: "minute",
70            value: minutes.to_string(),
71            span_start: Some(minutes_span.start()),
72            span_end: Some(minutes_span.end()),
73        })
74    } else if seconds >= Second::per_t(Minute) {
75        Err(Error::InvalidComponent {
76            name: "second",
77            value: seconds.to_string(),
78            span_start: Some(seconds_span.start()),
79            span_end: Some(seconds_span.end()),
80        })
81    } else {
82        Ok(Offset {
83            hours: sign * hours,
84            minutes: sign * minutes,
85            seconds: sign * seconds,
86        })
87    }
88}
89
90impl ToTokenStream for Offset {
91    fn into_token_stream(self) -> TokenStream {
92        // Safety: The parser checks each component's range and applies the same sign to all.
93        quote! {
94            unsafe {
95                ::time::UtcOffset::__from_hms_unchecked(
96                    #(self.hours),
97                    #(self.minutes),
98                    #(self.seconds),
99                )
100            }
101        }
102    }
103}