Skip to main content

time_macros/
timestamp.rs

1use std::iter::Peekable;
2
3use proc_macro::{TokenTree, token_stream};
4use time_core::unit::*;
5use time_core::util::days_in_year;
6
7use crate::date::{Date, MAX_YEAR, MIN_YEAR};
8use crate::helpers::{consume_punct, parse_number};
9use crate::time::Time;
10use crate::to_tokens::ToTokenStream;
11use crate::utc_datetime::UtcDateTime;
12use crate::{Error, utc_datetime};
13
14const MAX: i64 = UtcDateTime {
15    date: Date {
16        year: MAX_YEAR,
17        ordinal: days_in_year(MAX_YEAR),
18    },
19    time: Time {
20        hour: 23,
21        minute: 59,
22        second: 59,
23        nanosecond: 999_999_999,
24    },
25}
26.to_timestamp()
27.0;
28
29const MIN: i64 = UtcDateTime {
30    date: Date {
31        year: MIN_YEAR,
32        ordinal: 1,
33    },
34    time: Time {
35        hour: 0,
36        minute: 0,
37        second: 0,
38        nanosecond: 0,
39    },
40}
41.to_timestamp()
42.0;
43
44pub(crate) struct Timestamp {
45    seconds: i64,
46    nanoseconds: u32,
47}
48
49pub(crate) fn parse(tokens: &mut Peekable<token_stream::IntoIter>) -> Result<Timestamp, Error> {
50    // If the input can be parsed as a date-time, do that. Otherwise, try to parse as a timestamp
51    // directly.
52
53    let mut tokens2 = tokens.clone();
54    if let Ok(udt) = utc_datetime::parse(&mut tokens2) {
55        *tokens = tokens2;
56        let (seconds, nanoseconds) = udt.to_timestamp();
57        return Ok(Timestamp {
58            seconds,
59            nanoseconds,
60        });
61    }
62
63    let (sign_span, is_negative) = match consume_punct('-', tokens) {
64        Ok(span) => (Some(span), true),
65        Err(_) => (None, false),
66    };
67    let (span, raw) = match tokens.next() {
68        Some(TokenTree::Literal(literal)) => (literal.span(), literal.to_string()),
69        Some(tree) => return Err(Error::UnexpectedToken { tree }),
70        None => return Err(Error::UnexpectedEndOfInput),
71    };
72    let (seconds_str, fractional) = match raw.split_once('.') {
73        Some((int, frac)) => (int, Some(frac)),
74        None => (raw.as_str(), None),
75    };
76
77    let seconds = parse_number::<i64>("timestamp", seconds_str)?;
78    let nanoseconds = if let Some(fractional) = fractional {
79        // It's simpler to rely on existing helpers than to reimplement everything here. We can't do
80        // this for the overall value due to the large range of valid timestamps, meaning that 9+
81        // digits of precision is not guaranteed.
82        let frac = format!("0.{fractional}");
83        let parsed = parse_number::<f64>("timestamp", &frac)?;
84        (parsed.fract() * Nanosecond::per_t::<f64>(Second)).round() as u32
85    } else {
86        0
87    };
88
89    let (seconds, nanoseconds) = match (is_negative, fractional.is_some()) {
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}