1extern crate alloc;
42extern crate proc_macro;
43
44use proc_macro::TokenStream;
45use quote::quote;
46
47use self::shared::{
48 util::array_str::Abbreviation, PosixDay, PosixDayTime, PosixDst,
49 PosixOffset, PosixRule, PosixTime, PosixTimeZone, TzifDateTime, TzifFixed,
50 TzifIndicator, TzifLocalTimeType, TzifOwned, TzifTransitionInfo,
51 TzifTransitionKind, TzifTransitionsOwned,
52};
53
54#[allow(dead_code)]
64mod shared;
65
66#[proc_macro]
68pub fn include(input: TokenStream) -> TokenStream {
69 let input = syn::parse_macro_input!(input as Include);
70 proc_macro::TokenStream::from(input.quote())
71}
72
73#[cfg(feature = "tzdb")]
75#[proc_macro]
76pub fn get(input: TokenStream) -> TokenStream {
77 let input = syn::parse_macro_input!(input as Get);
78 proc_macro::TokenStream::from(input.quote())
79}
80
81#[derive(Debug)]
83struct Include {
84 tzif: TzifOwned,
85}
86
87impl Include {
88 fn from_path_only(path: &str) -> Result<Include, String> {
89 const NEEDLE: &str = "zoneinfo/";
90
91 let Some(zoneinfo) = path.rfind(NEEDLE) else {
92 return Err(format!(
93 "could not extract IANA time zone identifier from \
94 file path `{path}` \
95 (could not find `zoneinfo` in path), \
96 please provide IANA time zone identifier as second \
97 parameter",
98 ));
99 };
100 let idstart = zoneinfo.saturating_add(NEEDLE.len());
101 let id = &path[idstart..];
102 Include::from_path_with_id(id, path)
103 }
104
105 fn from_path_with_id(id: &str, path: &str) -> Result<Include, String> {
106 let id = id.to_string();
107 let data = std::fs::read(path)
108 .map_err(|e| format!("failed to read {path}: {e}"))?;
109 let tzif = TzifOwned::parse(Some(id.clone()), &data).map_err(|e| {
110 format!("failed to parse TZif data from {path}: {e}")
111 })?;
112 Ok(Include { tzif })
113 }
114
115 fn quote(&self) -> proc_macro2::TokenStream {
116 self.tzif.quote()
117 }
118}
119
120impl syn::parse::Parse for Include {
121 fn parse(input: syn::parse::ParseStream) -> syn::Result<Include> {
122 let lit1 = input.parse::<syn::LitStr>()?.value();
123 if !input.lookahead1().peek(syn::Token![,]) {
124 return Ok(
125 Include::from_path_only(&lit1).map_err(|e| input.error(e))?
126 );
127 }
128 input.parse::<syn::Token![,]>()?;
129 if input.is_empty() {
130 return Ok(
131 Include::from_path_only(&lit1).map_err(|e| input.error(e))?
132 );
133 }
134 let lit2 = input.parse::<syn::LitStr>()?.value();
135 if input.lookahead1().peek(syn::Token![,]) {
137 input.parse::<syn::Token![,]>()?;
138 }
139 Ok(Include::from_path_with_id(&lit2, &lit1)
140 .map_err(|e| input.error(e))?)
141 }
142}
143
144#[cfg(feature = "tzdb")]
146#[derive(Debug)]
147struct Get {
148 tzif: TzifOwned,
149}
150
151#[cfg(feature = "tzdb")]
152impl Get {
153 fn from_id(id: &str) -> Result<Get, String> {
154 let (id, data) = jiff_tzdb::get(id).ok_or_else(|| {
155 format!("could not find time zone `{id}` in bundled tzdb")
156 })?;
157 let id = id.to_string();
158 let tzif = TzifOwned::parse(Some(id.clone()), &data).map_err(|e| {
159 format!("failed to parse TZif data from bundled `{id}`: {e}")
160 })?;
161 Ok(Get { tzif })
162 }
163
164 fn quote(&self) -> proc_macro2::TokenStream {
165 self.tzif.quote()
166 }
167}
168
169#[cfg(feature = "tzdb")]
170impl syn::parse::Parse for Get {
171 fn parse(input: syn::parse::ParseStream) -> syn::Result<Get> {
172 let lit1 = input.parse::<syn::LitStr>()?.value();
173 if input.lookahead1().peek(syn::Token![,]) {
174 input.parse::<syn::Token![,]>()?;
175 }
176 Ok(Get::from_id(&lit1).map_err(|e| input.error(e))?)
177 }
178}
179
180impl TzifOwned {
184 fn quote(&self) -> proc_macro2::TokenStream {
185 let TzifOwned { ref fixed, ref types, ref transitions } = *self;
186 let fixed = fixed.quote();
187 let types = types.iter().map(TzifLocalTimeType::quote);
188 let transitions = transitions.quote();
189 quote! {
190 {
191 static TZ: jiff::tz::TimeZone =
192 jiff::tz::TimeZone::__internal_from_tzif(
193 &jiff::shared::TzifStatic {
194 fixed: #fixed,
195 types: &[#(#types),*],
196 transitions: #transitions,
197 }.into_jiff()
198 );
199 unsafe { TZ.copy() }
220 }
221 }
222 }
223}
224
225impl TzifFixed<String, Abbreviation> {
226 fn quote(&self) -> proc_macro2::TokenStream {
227 let TzifFixed {
228 ref name,
229 version,
230 checksum,
231 ref designations,
232 ref posix_tz,
233 } = *self;
234 let name = name.as_ref().unwrap();
235 let posix_tz = posix_tz
236 .as_ref()
237 .map(|tz| {
238 let tz = tz.quote();
239 quote!(Some(#tz))
240 })
241 .unwrap_or_else(|| quote!(None));
242 quote! {
243 jiff::shared::TzifFixed {
244 name: Some(#name),
245 version: #version,
246 checksum: #checksum,
247 designations: #designations,
248 posix_tz: #posix_tz,
249 }
250 }
251 }
252}
253
254impl TzifTransitionsOwned {
255 fn quote(&self) -> proc_macro2::TokenStream {
256 let TzifTransitionsOwned {
257 ref timestamps,
258 ref civil_starts,
259 ref civil_ends,
260 ref infos,
261 } = *self;
262 let civil_starts: Vec<_> =
263 civil_starts.iter().map(TzifDateTime::quote).collect();
264 let civil_ends: Vec<_> =
265 civil_ends.iter().map(TzifDateTime::quote).collect();
266 let infos: Vec<_> =
267 infos.iter().map(TzifTransitionInfo::quote).collect();
268 quote! {
269 jiff::shared::TzifTransitions {
270 timestamps: &[#(#timestamps),*],
271 civil_starts: &[#(#civil_starts),*],
272 civil_ends: &[#(#civil_ends),*],
273 infos: &[#(#infos),*],
274 }
275 }
276 }
277}
278
279impl TzifLocalTimeType {
280 fn quote(&self) -> proc_macro2::TokenStream {
281 let TzifLocalTimeType {
282 offset,
283 is_dst,
284 ref designation,
285 ref indicator,
286 } = *self;
287 let desig_start = designation.0;
288 let desig_end = designation.1;
289 let indicator = indicator.quote();
290 quote! {
291 jiff::shared::TzifLocalTimeType {
292 offset: #offset,
293 is_dst: #is_dst,
294 designation: (#desig_start, #desig_end),
295 indicator: #indicator,
296 }
297 }
298 }
299}
300
301impl TzifIndicator {
302 fn quote(&self) -> proc_macro2::TokenStream {
303 match *self {
304 TzifIndicator::LocalWall => quote! {
305 jiff::shared::TzifIndicator::LocalWall
306 },
307 TzifIndicator::LocalStandard => quote! {
308 jiff::shared::TzifIndicator::LocalStandard
309 },
310 TzifIndicator::UTStandard => quote! {
311 jiff::shared::TzifIndicator::UTStandard
312 },
313 }
314 }
315}
316
317impl TzifTransitionInfo {
318 fn quote(&self) -> proc_macro2::TokenStream {
319 let TzifTransitionInfo { type_index, kind } = *self;
320 let kind = kind.quote();
321 quote! {
322 jiff::shared::TzifTransitionInfo {
323 type_index: #type_index,
324 kind: #kind,
325 }
326 }
327 }
328}
329
330impl TzifTransitionKind {
331 fn quote(&self) -> proc_macro2::TokenStream {
332 match *self {
333 TzifTransitionKind::Unambiguous => quote! {
334 jiff::shared::TzifTransitionKind::Unambiguous
335 },
336 TzifTransitionKind::Gap => quote! {
337 jiff::shared::TzifTransitionKind::Gap
338 },
339 TzifTransitionKind::Fold => quote! {
340 jiff::shared::TzifTransitionKind::Fold
341 },
342 }
343 }
344}
345
346impl TzifDateTime {
347 fn quote(&self) -> proc_macro2::TokenStream {
348 let year = self.year();
349 let month = self.month();
350 let day = self.day();
351 let hour = self.hour();
352 let minute = self.minute();
353 let second = self.second();
354 quote! {
355 jiff::shared::TzifDateTime::new(
356 #year,
357 #month,
358 #day,
359 #hour,
360 #minute,
361 #second,
362 )
363 }
364 }
365}
366
367impl PosixTimeZone<Abbreviation> {
368 fn quote(&self) -> proc_macro2::TokenStream {
369 let PosixTimeZone { ref std_abbrev, ref std_offset, ref dst } = *self;
370 let std_abbrev = std_abbrev.as_str();
371 let std_offset = std_offset.quote();
372 let dst = dst
373 .as_ref()
374 .map(|dst| {
375 let dst = dst.quote();
376 quote!(Some(#dst))
377 })
378 .unwrap_or_else(|| quote!(None));
379 quote! {
380 jiff::shared::PosixTimeZone {
381 std_abbrev: #std_abbrev,
382 std_offset: #std_offset,
383 dst: #dst,
384 }
385 }
386 }
387}
388
389impl PosixDst<Abbreviation> {
390 fn quote(&self) -> proc_macro2::TokenStream {
391 let PosixDst { ref abbrev, ref offset, ref rule } = *self;
392 let abbrev = abbrev.as_str();
393 let offset = offset.quote();
394 let rule = rule.quote();
395 quote! {
396 jiff::shared::PosixDst {
397 abbrev: #abbrev,
398 offset: #offset,
399 rule: #rule,
400 }
401 }
402 }
403}
404
405impl PosixRule {
406 fn quote(&self) -> proc_macro2::TokenStream {
407 let start = self.start.quote();
408 let end = self.end.quote();
409 quote! {
410 jiff::shared::PosixRule { start: #start, end: #end }
411 }
412 }
413}
414
415impl PosixDayTime {
416 fn quote(&self) -> proc_macro2::TokenStream {
417 let PosixDayTime { ref date, ref time } = *self;
418 let date = date.quote();
419 let time = time.quote();
420 quote! {
421 jiff::shared::PosixDayTime { date: #date, time: #time }
422 }
423 }
424}
425
426impl PosixDay {
427 fn quote(&self) -> proc_macro2::TokenStream {
428 match *self {
429 PosixDay::JulianOne(day) => quote! {
430 jiff::shared::PosixDay::JulianOne(#day)
431 },
432 PosixDay::JulianZero(day) => quote! {
433 jiff::shared::PosixDay::JulianZero(#day)
434 },
435 PosixDay::WeekdayOfMonth { month, week, weekday } => quote! {
436 jiff::shared::PosixDay::WeekdayOfMonth {
437 month: #month,
438 week: #week,
439 weekday: #weekday,
440 }
441 },
442 }
443 }
444}
445
446impl PosixTime {
447 fn quote(&self) -> proc_macro2::TokenStream {
448 let PosixTime { second } = *self;
449 quote! {
450 jiff::shared::PosixTime { second: #second }
451 }
452 }
453}
454
455impl PosixOffset {
456 fn quote(&self) -> proc_macro2::TokenStream {
457 let PosixOffset { second } = *self;
458 quote! {
459 jiff::shared::PosixOffset { second: #second }
460 }
461 }
462}