Skip to main content

time_macros/
timestamp.rs

1use std::iter::{self, Peekable};
2
3use proc_macro::{TokenTree, token_stream};
4use time_core::util::days_in_year;
5
6use crate::date::{Date, MAX_YEAR, MIN_YEAR};
7use crate::helpers::{consume_punct, parse_number};
8use crate::time::Time;
9use crate::to_tokens::ToTokenStream;
10use crate::utc_datetime::UtcDateTime;
11use crate::{Error, utc_datetime};
12
13const MAX: i64 = UtcDateTime {
14    date: Date {
15        year: MAX_YEAR,
16        ordinal: days_in_year(MAX_YEAR),
17    },
18    time: Time {
19        hour: 23,
20        minute: 59,
21        second: 59,
22        nanosecond: 999_999_999,
23    },
24}
25.to_timestamp()
26.0;
27
28const MIN: i64 = UtcDateTime {
29    date: Date {
30        year: MIN_YEAR,
31        ordinal: 1,
32    },
33    time: Time {
34        hour: 0,
35        minute: 0,
36        second: 0,
37        nanosecond: 0,
38    },
39}
40.to_timestamp()
41.0;
42
43/// A parsed timestamp.
44///
45/// The seconds must be in `MIN..=MAX`, and the nanoseconds in `0..1_000_000_000`. These ranges are
46/// required by the unchecked `Timestamp` constructor used when emitting this value.
47pub(crate) struct Timestamp {
48    seconds: i64,
49    nanoseconds: u32,
50}
51
52pub(crate) fn parse(tokens: &mut Peekable<token_stream::IntoIter>) -> Result<Timestamp, Error> {
53    // If the input can be parsed as a date-time, do that. Otherwise, try to parse as a timestamp
54    // directly.
55
56    let mut tokens2 = tokens.clone();
57    if let Ok(udt) = utc_datetime::parse(&mut tokens2) {
58        *tokens = tokens2;
59        let (seconds, nanoseconds) = udt.to_timestamp();
60        return Ok(Timestamp {
61            seconds,
62            nanoseconds,
63        });
64    }
65
66    let (sign_span, is_negative) = match consume_punct('-', tokens) {
67        Ok(span) => (Some(span), true),
68        Err(_) => (None, false),
69    };
70    let (span, raw) = match tokens.next() {
71        Some(TokenTree::Literal(literal)) => (literal.span(), literal.to_string()),
72        Some(tree) => return Err(Error::UnexpectedToken { tree }),
73        None => return Err(Error::UnexpectedEndOfInput),
74    };
75    let (seconds_str, fractional) = match raw.split_once('.') {
76        Some((int, frac)) => (int, Some(frac)),
77        None => (raw.as_str(), None),
78    };
79
80    let seconds = parse_number::<i64>("timestamp", seconds_str)?;
81    let nanoseconds = if let Some(fractional) = fractional {
82        let nanoseconds = fractional
83            .replace('_', "")
84            .chars()
85            .chain(iter::repeat('0'))
86            .take(9)
87            .collect::<String>();
88        parse_number::<u32>("timestamp", &nanoseconds)?
89    } else {
90        0
91    };
92
93    let (seconds, nanoseconds) = match (is_negative, nanoseconds != 0) {
94        (true, true) => (-seconds - 1, 1_000_000_000 - nanoseconds),
95        (true, false) => (-seconds, 0),
96        (false, _) => (seconds, nanoseconds),
97    };
98
99    if seconds > MAX {
100        return Err(Error::Custom {
101            message: "timestamp is too large".into(),
102            span_start: sign_span.or(Some(span)),
103            span_end: Some(span),
104        });
105    }
106    if seconds < MIN {
107        return Err(Error::Custom {
108            message: "timestamp is too small".into(),
109            span_start: sign_span.or(Some(span)),
110            span_end: Some(span),
111        });
112    }
113
114    Ok(Timestamp {
115        seconds,
116        nanoseconds,
117    })
118}
119
120impl ToTokenStream for Timestamp {
121    fn into_token_stream(self) -> proc_macro::TokenStream {
122        // Safety: The parser checks the seconds range and normalizes the nanoseconds before
123        // constructing `Timestamp`.
124        quote! {
125            unsafe {
126                ::time::Timestamp::__new_unchecked(
127                    #(self.seconds),
128                    #(self.nanoseconds)
129                )
130            }
131        }
132    }
133}