1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
//! This crate provides macros for getting compile time information.
//!
//! You can get the compile time either as
//! [`time::Date`](time::Date), [`time::Time`](time::Time),
//! [`time::OffsetDateTime`](time::OffsetDateTime), string, or UNIX timestamp.
//!
//! You can get the Rust compiler version either as
//! [`semver::Version`](semver::Version) or string,
//! and the individual version parts as integer literals or strings, respectively.
//!
//! # Example
//!
//! ```
//! let compile_datetime = compile_time::datetime_str!();
//! let rustc_version = compile_time::rustc_version_str!();
//!
//! println!("Compiled using Rust {rustc_version} on {compile_datetime}.");
//! ```

extern crate proc_macro;

use once_cell::sync::Lazy;
use proc_macro::TokenStream;
use quote::{format_ident, quote, ToTokens};
use time::{macros::format_description, OffsetDateTime};

static COMPILE_TIME: Lazy<OffsetDateTime> = Lazy::new(OffsetDateTime::now_utc);
static RUSTC_VERSION: Lazy<rustc_version::Result<rustc_version::Version>> = Lazy::new(rustc_version::version);

/// Compile date as `time::Date`.
///
/// # Example
///
/// ```
/// const COMPILE_DATE: time::Date = compile_time::date!();
///
/// let year = COMPILE_DATE.year();
/// let month = COMPILE_DATE.month();
/// let day = COMPILE_DATE.day();
/// println!("Compiled on {month} {day}, {year}.");
/// ```
#[proc_macro]
pub fn date(_item: TokenStream) -> TokenStream {
  let date = COMPILE_TIME.date();

  let year = date.year();
  let month = format_ident!("{}", format!("{:?}", date.month()));
  let day = date.day();

  quote! {
    match ::time::Date::from_calendar_date(#year, ::time::Month::#month, #day) {
      Ok(date) => date,
      _ => ::core::unreachable!(),
    }
  }
  .into()
}

/// Compile date as `&'static str` in `yyyy-MM-dd` format.
///
/// # Example
///
/// ```
/// const COMPILE_DATE: time::Date = compile_time::date!();
///
/// let year = COMPILE_DATE.year();
/// let month: u8 = COMPILE_DATE.month().into();
/// let day = COMPILE_DATE.day();
/// let date_string = format!("{year:04}-{month:02}-{day:02}");
///
/// assert_eq!(compile_time::date_str!(), date_string);
/// ```
#[proc_macro]
pub fn date_str(_item: TokenStream) -> TokenStream {
  let date = COMPILE_TIME.date();

  let fmt = format_description!("[year]-[month]-[day]");
  let date_str = date.format(&fmt).unwrap();

  quote! { #date_str }.into()
}

/// Compile time as `time::Time`.
///
/// # Example
///
/// ```
/// const COMPILE_TIME: time::Time = compile_time::time!();
///
/// let hour = COMPILE_TIME.hour();
/// let minute = COMPILE_TIME.minute();
/// let second = COMPILE_TIME.second();
/// println!("Compiled at {hour:02}:{minute:02}:{second:02}.");
/// ```
#[proc_macro]
pub fn time(_item: TokenStream) -> TokenStream {
  let time = COMPILE_TIME.time();

  let hour = time.hour();
  let minute = time.minute();
  let second = time.second();

  quote! {
    match ::time::Time::from_hms(#hour, #minute, #second) {
      Ok(time) => time,
      _ => ::core::unreachable!(),
    }
  }
  .into()
}

/// Compile time as `&'static str` in `hh:mm:ss` format.
///
/// # Example
///
/// ```
/// const COMPILE_TIME: time::Time = compile_time::time!();
///
/// let hour = COMPILE_TIME.hour();
/// let minute = COMPILE_TIME.minute();
/// let second = COMPILE_TIME.second();
/// let time_string = format!("{hour:02}:{minute:02}:{second:02}");
///
/// assert_eq!(compile_time::time_str!(), time_string);
/// ```
#[proc_macro]
pub fn time_str(_item: TokenStream) -> TokenStream {
  let time = COMPILE_TIME.time();

  let fmt = format_description!("[hour]:[minute]:[second]");
  let time_str = time.format(&fmt).unwrap();

  quote! { #time_str }.into()
}

/// Compile date and time as `time::OffsetDateTime`.
///
/// # Example
///
/// ```
/// const COMPILE_DATETIME: time::OffsetDateTime = compile_time::datetime!();
///
/// let year = COMPILE_DATETIME.year();
/// let month = COMPILE_DATETIME.month();
/// let day = COMPILE_DATETIME.day();
/// let hour = COMPILE_DATETIME.hour();
/// let minute = COMPILE_DATETIME.minute();
/// let second = COMPILE_DATETIME.second();
/// println!("Compiled at {hour:02}:{minute:02}:{second:02} on {month} {day}, {year}.");
/// #
/// # // Evaluation is only done once.
/// # std::thread::sleep(std::time::Duration::from_secs(1));
/// # assert_eq!(COMPILE_DATETIME, compile_time::datetime!());
/// #
/// # // Additional sanity check.
/// # let now = time::OffsetDateTime::now_utc();
/// # let yesterday = now.saturating_sub(time::Duration::days(1));
/// # assert!(COMPILE_DATETIME > yesterday);
/// # assert!(COMPILE_DATETIME < now);
/// ```
#[proc_macro]
pub fn datetime(_item: TokenStream) -> TokenStream {
  let datetime = *COMPILE_TIME;

  let year = datetime.year();
  let month = format_ident!("{}", format!("{:?}", datetime.month()));
  let day = datetime.day();

  let hour = datetime.hour();
  let minute = datetime.minute();
  let second = datetime.second();

  let date = quote! {
    match ::time::Date::from_calendar_date(#year, ::time::Month::#month, #day) {
      Ok(date) => date,
      _ => ::core::unreachable!(),
    }
  };

  let time = quote! {
    match ::time::Time::from_hms(#hour, #minute, #second) {
      Ok(time) => time,
      _ => ::core::unreachable!(),
    }
  };

  quote! {
    ::time::PrimitiveDateTime::new(#date, #time).assume_utc()
  }
  .into()
}

/// Compile time as `&'static str` in `yyyy-MM-ddThh:mm:ssZ` format.
///
/// # Example
///
/// ```
/// const COMPILE_DATE_STRING: &str = compile_time::date_str!();
/// const COMPILE_TIME_STRING: &str = compile_time::time_str!();
///
/// let datetime_string = format!("{COMPILE_DATE_STRING}T{COMPILE_TIME_STRING}Z");
/// assert_eq!(compile_time::datetime_str!(), datetime_string);
/// ```
#[proc_macro]
pub fn datetime_str(_item: TokenStream) -> TokenStream {
  let datetime = *COMPILE_TIME;

  let fmt = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]Z");
  let datetime_str = datetime.format(&fmt).unwrap();

  quote! { #datetime_str }.into()
}

/// Compile date and time as UNIX timestamp in seconds.
///
/// # Example
///
/// ```
/// const COMPILE_DATETIME: time::OffsetDateTime = compile_time::datetime!();
///
/// assert_eq!(compile_time::unix!(), COMPILE_DATETIME.unix_timestamp());
/// ```
#[proc_macro]
pub fn unix(_item: TokenStream) -> TokenStream {
  let datetime = *COMPILE_TIME;

  let unix_timestamp = proc_macro2::Literal::i64_unsuffixed(datetime.unix_timestamp());

  quote! {
    #unix_timestamp
  }
  .into()
}

/// Rust compiler version as `semver::Version`.
///
/// # Example
///
/// ```
/// let rustc_version: semver::Version = compile_time::rustc_version!();
/// assert_eq!(rustc_version, rustc_version::version().unwrap());
/// ```
#[proc_macro]
pub fn rustc_version(_item: TokenStream) -> TokenStream {
  let rustc_version::Version { major, minor, patch, pre, build } = match &*RUSTC_VERSION {
    Ok(rustc_version) => rustc_version,
    Err(err) => panic!("Failed to get version: {}", err),
  };

  let pre = if pre.is_empty() {
    quote! { ::semver::Prerelease::EMPTY }
  } else {
    let pre = pre.as_str();
    quote! {
      if let Ok(pre) = ::semver::Prerelease::new(#pre) {
        pre
      } else {
        ::core::unreachable!()
      }
    }
  };

  let build = if build.is_empty() {
    quote! { ::semver::BuildMetadata::EMPTY }
  } else {
    let build = build.as_str();
    quote! {
      if let Ok(build) = ::semver::BuildMetadata::new(#build) {
        build
      } else {
        ::core::unreachable!()
      }
    }
  };

  quote! {
    ::semver::Version {
      major: #major,
      minor: #minor,
      patch: #patch,
      pre: #pre,
      build: #build,
    }
  }
  .into()
}

/// Rust compiler version as `&'static str`.
///
/// # Example
///
/// ```
/// const RUSTC_VERSION_STRING: &str = compile_time::rustc_version_str!();
/// assert_eq!(RUSTC_VERSION_STRING, compile_time::rustc_version_str!());
/// ```
#[proc_macro]
pub fn rustc_version_str(_item: TokenStream) -> TokenStream {
  let rustc_version = match &*RUSTC_VERSION {
    Ok(rustc_version) => rustc_version,
    Err(err) => panic!("Failed to get version: {}", err),
  };

  let rustc_version_string = rustc_version.to_string();
  quote! { #rustc_version_string }.into()
}

/// Rust compiler major version as integer literal.
///
/// # Example
///
/// ```
/// let rustc_version: semver::Version = compile_time::rustc_version!();
/// assert_eq!(rustc_version.major, compile_time::rustc_version_major!());
/// ```
#[proc_macro]
pub fn rustc_version_major(_item: TokenStream) -> TokenStream {
  let major = match &*RUSTC_VERSION {
    Ok(rustc_version) => rustc_version.major,
    Err(err) => panic!("Failed to get version: {}", err),
  };

  proc_macro2::Literal::u64_unsuffixed(major).to_token_stream().into()
}

/// Rust compiler minor version as integer literal.
///
/// # Example
///
/// ```
/// let rustc_version: semver::Version = compile_time::rustc_version!();
/// assert_eq!(rustc_version.minor, compile_time::rustc_version_minor!());
/// ```
#[proc_macro]
pub fn rustc_version_minor(_item: TokenStream) -> TokenStream {
  let minor = match &*RUSTC_VERSION {
    Ok(rustc_version) => rustc_version.minor,
    Err(err) => panic!("Failed to get version: {}", err),
  };

  proc_macro2::Literal::u64_unsuffixed(minor).to_token_stream().into()
}

/// Rust compiler patch version as integer literal.
///
/// # Example
///
/// ```
/// let rustc_version: semver::Version = compile_time::rustc_version!();
/// assert_eq!(rustc_version.minor, compile_time::rustc_version_minor!());
/// ```
#[proc_macro]
pub fn rustc_version_patch(_item: TokenStream) -> TokenStream {
  let patch = match &*RUSTC_VERSION {
    Ok(rustc_version) => rustc_version.patch,
    Err(err) => panic!("Failed to get version: {}", err),
  };

  proc_macro2::Literal::u64_unsuffixed(patch).to_token_stream().into()
}

/// Rust compiler pre version as `&'static str`.
///
/// # Example
///
/// ```
/// let rustc_version: semver::Version = compile_time::rustc_version!();
/// assert_eq!(rustc_version.pre.as_str(), compile_time::rustc_version_pre!());
/// ```
#[proc_macro]
pub fn rustc_version_pre(_item: TokenStream) -> TokenStream {
  let pre = match &*RUSTC_VERSION {
    Ok(rustc_version) => rustc_version.pre.as_str(),
    Err(err) => panic!("Failed to get version: {}", err),
  };

  quote! { #pre }.into()
}

/// Rust compiler build version as `&'static str`.
///
/// # Example
///
/// ```
/// let rustc_version: semver::Version = compile_time::rustc_version!();
/// assert_eq!(rustc_version.build.as_str(), compile_time::rustc_version_build!());
/// ```
#[proc_macro]
pub fn rustc_version_build(_item: TokenStream) -> TokenStream {
  let build = match &*RUSTC_VERSION {
    Ok(rustc_version) => rustc_version.build.as_str(),
    Err(err) => panic!("Failed to get version: {}", err),
  };

  quote! { #build }.into()
}