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
//! This crate provides macros for getting the compile time, at compile time, either as
//! [`time::Date`](time::Date), [`time::Time`](time::Time),
//! [`time::OffsetDateTime`](time::OffsetDateTime), string, or UNIX timestamp.
//!
//! # Example
//!
//! ```
//! const COMPILE_DATE: time::Date = compile_time::date!();
//! const COMPILE_TIME: time::Time = compile_time::time!();
//! const COMPILE_DATETIME: time::OffsetDateTime = compile_time::datetime!();
//!
//! const COMPILE_DATE_STRING: &str = compile_time::date_str!();
//! const COMPILE_TIME_STRING: &str = compile_time::time_str!();
//! const COMPILE_DATETIME_STRING: &str = compile_time::datetime_str!();
//!
//! // Evaluation is only done once.
//! # std::thread::sleep(std::time::Duration::from_secs(1));
//! assert_eq!(COMPILE_DATETIME, compile_time::datetime!());
//!
//! // Date string is formatted as yyyy-MM-dd.
//! let year = COMPILE_DATETIME.year();
//! let month: u8 = COMPILE_DATETIME.month().into();
//! let day = COMPILE_DATETIME.day();
//! let date_string = format!("{year:04}-{month:02}-{day:02}");
//! assert_eq!(COMPILE_DATE_STRING, date_string);
//!
//! // Time is formatted as hh:mm::ss.
//! let hour = COMPILE_DATETIME.hour();
//! let minute = COMPILE_DATETIME.minute();
//! let second = COMPILE_DATETIME.second();
//! let time_string = format!("{hour:02}:{minute:02}:{second:02}");
//! assert_eq!(COMPILE_TIME_STRING, time_string);
//!
//! // Date-time is formatted as yyyy-MM-ddThh:mm::ssZ.
//! let datetime_string = format!("{date_string}T{time_string}Z");
//! assert_eq!(COMPILE_DATETIME_STRING, datetime_string);
//!
//! // UNIX time in seconds.
//! assert_eq!(COMPILE_DATETIME.unix_timestamp(), compile_time::unix!());
//! #
//! # // 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);
//! ```

extern crate proc_macro;

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

static COMPILE_TIME: Lazy<OffsetDateTime> = Lazy::new(OffsetDateTime::now_utc);

/// Compile date as `time::Date`.
#[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();

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

  proc_macro::TokenStream::from(output)
}

/// Compile date as `&'static str` in `YYY-MM-DD` format.
#[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();

  let output = quote! {
    #date_str
  };

  proc_macro::TokenStream::from(output)
}

/// Compile time as `time::Time`.
#[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();

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

  proc_macro::TokenStream::from(output)
}

/// Compile time as `&'static str` in `hh:mm:ss` format.
#[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();

  let output = quote! {
    #time_str
  };

  proc_macro::TokenStream::from(output)
}

/// Compile date and time as `time::OffsetDateTime`.
#[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!(),
    }
  };

  let output = quote! {
    ::time::PrimitiveDateTime::new(#date, #time).assume_utc()
  };

  proc_macro::TokenStream::from(output)
}

/// Compile time as `&'static str` in `yyyy-MM-ddThh:mm:ssZ` format.
#[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();

  let output = quote! {
    #datetime_str
  };

  proc_macro::TokenStream::from(output)
}

/// Compile date and time as UNIX timestamp in seconds.
#[proc_macro]
pub fn unix(_item: TokenStream) -> TokenStream {
  let datetime = *COMPILE_TIME;

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

  let output = quote! {
    #unix_timestamp
  };

  proc_macro::TokenStream::from(output)
}