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
43pub(crate) struct Timestamp {
44    seconds: i64,
45    nanoseconds: u32,
46}
47
48pub(crate) fn parse(tokens: &mut Peekable<token_stream::IntoIter>) -> Result<Timestamp, Error> {
49    // If the input can be parsed as a date-time, do that. Otherwise, try to parse as a timestamp
50    // directly.
51
52    let mut tokens2 = tokens.clone();
53    if let Ok(udt) = utc_datetime::parse(&mut tokens2) {
54        *tokens = tokens2;
55        let (seconds, nanoseconds) = udt.to_timestamp();
56        return Ok(Timestamp {
57            seconds,
58            nanoseconds,
59        });
60    }
61
62    let (sign_span, is_negative) = match consume_punct('-', tokens) {
63        Ok(span) => (Some(span), true),
64        Err(_) => (None, false),
65    };
66    let (span, raw) = match tokens.next() {
67        Some(TokenTree::Literal(literal)) => (literal.span(), literal.to_string()),
68        Some(tree) => return Err(Error::UnexpectedToken { tree }),
69        None => return Err(Error::UnexpectedEndOfInput),
70    };
71    let (seconds_str, fractional) = match raw.split_once('.') {
72        Some((int, frac)) => (int, Some(frac)),
73        None => (raw.as_str(), None),
74    };
75
76    let seconds = parse_number::<i64>("timestamp", seconds_str)?;
77    let nanoseconds = if let Some(fractional) = fractional {
78        let nanoseconds = fractional
79            .replace('_', "")
80            .chars()
81            .chain(iter::repeat('0'))
82            .take(9)
83            .collect::<String>();
84        parse_number::<u32>("timestamp", &nanoseconds)?
85    } else {
86        0
87    };
88
89    let (seconds, nanoseconds) = match (is_negative, nanoseconds != 0) {
90        (true, true) => (-seconds - 1, 1_000_000_000 - nanoseconds),
91        (true, false) => (-seconds, 0),
92        (false, _) => (seconds, nanoseconds),
93    };
94
95    if seconds > MAX {
96        return Err(Error::Custom {
97            message: "timestamp is too large".into(),
98            span_start: sign_span.or(Some(span)),
99            span_end: Some(span),
100        });
101    }
102    if seconds < MIN {
103        return Err(Error::Custom {
104            message: "timestamp is too small".into(),
105            span_start: sign_span.or(Some(span)),
106            span_end: Some(span),
107        });
108    }
109
110    Ok(Timestamp {
111        seconds,
112        nanoseconds,
113    })
114}
115
116impl ToTokenStream for Timestamp {
117    fn append_to(self, ts: &mut proc_macro::TokenStream) {
118        quote_append! { ts
119            unsafe {
120                ::time::Timestamp::__new_unchecked(
121                    #(self.seconds),
122                    #(self.nanoseconds)
123                )
124            }
125        }
126    }
127}