datetime_rs_codegen/
lib.rs1use std::sync::LazyLock;
7
8use proc_macro2::TokenStream;
9use proc_macro2::TokenTree;
10use quote::quote;
11use regex::Regex;
12use syn::Result;
13use syn::Token;
14use syn::parse::Parse;
15use syn::parse::ParseStream;
16
17pub fn nanoseconds(tokens: TokenStream) -> TokenStream {
22 let delta = match syn::parse2::<Delta>(tokens) {
23 Ok(delta) => delta,
24 Err(err) => return err.into_compile_error(),
25 };
26 let nanos = delta.nanoseconds;
27
28 match nanos < 0 {
34 true => {
35 let nanos = nanos.abs();
36 quote! { const { - #nanos }}
37 },
38 false => quote! { #nanos },
39 }
40}
41
42pub struct Delta {
43 nanoseconds: i128,
44}
45
46impl Delta {
47 pub const fn seconds(&self) -> i64 {
49 self.nanoseconds.div_euclid(1_000_000_000) as i64
50 }
51
52 pub const fn nanos(&self) -> u32 {
54 self.nanoseconds.rem_euclid(1_000_000_000) as u32
55 }
56}
57
58#[derive(Debug, Default)]
59struct Pieces {
60 days: i64,
61 hours: i64,
62 minutes: i64,
63 seconds: i64,
64 nanos: u32,
65}
66
67impl Pieces {
68 fn as_seconds(&self) -> i64 {
69 (self.days * 86_400) + (self.hours * 3_600) + (self.minutes * 60) + self.seconds
70 }
71}
72
73impl From<Pieces> for Delta {
74 fn from(p: Pieces) -> Self {
75 Self { nanoseconds: p.as_seconds() as i128 * 1_000_000_000 + p.nanos as i128 }
76 }
77}
78
79impl Parse for Delta {
80 fn parse(input: ParseStream) -> Result<Self> {
81 let signum = match input.peek(Token![+]) || input.peek(Token![-]) {
83 true => match input.parse::<syn::BinOp>()? {
84 syn::BinOp::Add(_) => 1,
85 syn::BinOp::Sub(_) => -1,
86 _ => unreachable!("Token must be + or -."),
87 },
88 false => 1,
89 };
90
91 macro_rules! err {
92 ($span:expr, $msg:literal $(,)? $($args:expr),*) => {
93 syn::Error::new($span, format!($msg, $($args),*))
94 }
95 }
96
97 let mut pieces = Pieces::default();
99 while let Ok(token) = input.parse::<TokenTree>() {
100 let delta = token.to_string();
101 let captures = (DELTA_STRING.captures(delta.as_str()))
102 .ok_or_else(|| err!(token.span(), "Invalid duration string: {delta}"))?;
103
104 macro_rules! capture_piece {
106 ($captures:ident[$index:literal] $trim:literal $p:ident $tokens:ident $unit:ident) => {{
107 let secs = $captures.get($index)
108 .map(|i| i.as_str().trim_end_matches($trim))
109 .map(|s| s.parse::<i64>().map_err(|_| {
110 let err_msg = stringify!(invalid $unit);
111 err!($tokens.span(), "{err_msg}")
112 }))
113 .transpose()?
114 .unwrap_or_default();
115 let current = $p.as_seconds();
116 if current != 0 && secs > current.abs() {
117 return Err(err!($tokens.span(), "Place only larger units of time before {}.", stringify!($unit)))?;
118 }
119 if secs != 0 && $p.$unit != 0 {
120 return Err(err!($tokens.span(), "Only declare {} once.", stringify!($unit)));
121 }
122 secs
123 }};
124 }
125 pieces.days += capture_piece!(captures[1] 'd' pieces token days) * signum;
126 pieces.hours += capture_piece!(captures[2] 'h' pieces token hours) * signum;
127 pieces.minutes += capture_piece!(captures[3] 'm' pieces token minutes) * signum;
128 let (secs, nanoseconds) = captures
129 .get(4)
130 .map(|s| -> syn::Result<(i64, u32)> {
131 let split = s.as_str().trim_end_matches('s').split('.').collect::<Vec<&str>>();
132 match split.len() == 1 {
133 true => Ok((
134 split[0].parse::<i64>().map_err(|_| err!(token.span(), "invalid seconds"))? * signum,
135 0,
136 )),
137 false => {
138 if split[1].len() > 9 {
139 Err(err!(token.span(), "Offset precision greater than nanoseconds"))?;
140 }
141 let mut s = split[0].parse::<i64>().unwrap() * signum;
142 let mut n = split[1].parse::<u32>().unwrap() * 10u32.pow(9 - split[1].len() as u32);
143 if signum == -1 && n != 0 {
145 s -= 1;
146 n = 1_000_000_000 - n;
147 }
148 Ok((s, n))
149 },
150 }
151 })
152 .transpose()?
153 .unwrap_or_default();
154
155 if pieces.as_seconds() != 0 && secs > pieces.as_seconds().abs() {
157 Err(err!(token.span(), "Place only larger units of time before seconds."))?;
158 }
159 if pieces.nanos > 0 && nanoseconds > 0 {
160 Err(err!(token.span(), "Fractional seconds may only be declared once."))?;
161 }
162
163 pieces.seconds += secs;
165 pieces.nanos += nanoseconds;
166
167 if input.peek(Token![,]) {
170 break;
171 }
172 }
173
174 Ok(pieces.into())
176 }
177}
178
179static DELTA_STRING: LazyLock<Regex> = LazyLock::new(|| {
180 Regex::new(r"^([\d]+d)?([\d]+h)?([\d]+m)?([\d]+\.?[\d]*s)?$").expect("valid regex")
181});