1extern crate alloc;
42extern crate proc_macro;
43
44use proc_macro::TokenStream;
45use quote::quote;
46
47use jcore::tz::{posix, tzif, TimeZoneId};
48
49#[proc_macro]
51pub fn include(input: TokenStream) -> TokenStream {
52 let input = syn::parse_macro_input!(input as Include);
53 proc_macro::TokenStream::from(input.quote())
54}
55
56#[cfg(feature = "tzdb")]
58#[proc_macro]
59pub fn get(input: TokenStream) -> TokenStream {
60 let input = syn::parse_macro_input!(input as Get);
61 proc_macro::TokenStream::from(input.quote())
62}
63
64#[derive(Debug)]
66struct Include {
67 tzif: tzif::MaybeNamedTimeZone,
68}
69
70impl Include {
71 fn from_path_only(path: &str) -> Result<Include, String> {
72 const NEEDLE: &str = "zoneinfo/";
73
74 let Some(zoneinfo) = path.rfind(NEEDLE) else {
75 return Err(format!(
76 "could not extract IANA time zone identifier from \
77 file path `{path}` \
78 (could not find `zoneinfo` in path), \
79 please provide IANA time zone identifier as second \
80 parameter",
81 ));
82 };
83 let idstart = zoneinfo.saturating_add(NEEDLE.len());
84 let id = &path[idstart..];
85 Include::from_path_with_id(id, path)
86 }
87
88 fn from_path_with_id(id: &str, path: &str) -> Result<Include, String> {
89 let id = TimeZoneId::new_or_heap(id);
90 let data = std::fs::read(path)
91 .map_err(|e| format!("failed to read {path}: {e}"))?;
92 let tzif = tzif::TimeZone::parse(&data)
93 .map_err(|e| {
94 format!("failed to parse TZif data from {path}: {e}")
95 })?
96 .into_named(id);
97 Ok(Include { tzif })
98 }
99
100 fn quote(&self) -> proc_macro2::TokenStream {
101 self.tzif.quote()
102 }
103}
104
105impl syn::parse::Parse for Include {
106 fn parse(input: syn::parse::ParseStream) -> syn::Result<Include> {
107 let lit1 = input.parse::<syn::LitStr>()?.value();
108 if !input.lookahead1().peek(syn::Token![,]) {
109 return Ok(
110 Include::from_path_only(&lit1).map_err(|e| input.error(e))?
111 );
112 }
113 input.parse::<syn::Token![,]>()?;
114 if input.is_empty() {
115 return Ok(
116 Include::from_path_only(&lit1).map_err(|e| input.error(e))?
117 );
118 }
119 let lit2 = input.parse::<syn::LitStr>()?.value();
120 if input.lookahead1().peek(syn::Token![,]) {
122 input.parse::<syn::Token![,]>()?;
123 }
124 Ok(Include::from_path_with_id(&lit2, &lit1)
125 .map_err(|e| input.error(e))?)
126 }
127}
128
129#[cfg(feature = "tzdb")]
131#[derive(Debug)]
132struct Get {
133 tzif: tzif::MaybeNamedTimeZone,
134}
135
136#[cfg(feature = "tzdb")]
137impl Get {
138 fn from_id(id: &str) -> Result<Get, String> {
139 let (id, data) = jiff_tzdb::get(id).ok_or_else(|| {
140 format!("could not find time zone `{id}` in bundled tzdb")
141 })?;
142 let id = TimeZoneId::statik(id);
143 let tzif = tzif::TimeZone::parse(&data)
144 .map_err(|e| {
145 format!("failed to parse TZif data from bundled `{id}`: {e}")
146 })?
147 .into_named(id);
148 Ok(Get { tzif })
149 }
150
151 fn quote(&self) -> proc_macro2::TokenStream {
152 self.tzif.quote()
153 }
154}
155
156#[cfg(feature = "tzdb")]
157impl syn::parse::Parse for Get {
158 fn parse(input: syn::parse::ParseStream) -> syn::Result<Get> {
159 let lit1 = input.parse::<syn::LitStr>()?.value();
160 if input.lookahead1().peek(syn::Token![,]) {
161 input.parse::<syn::Token![,]>()?;
162 }
163 Ok(Get::from_id(&lit1).map_err(|e| input.error(e))?)
164 }
165}
166
167trait Quote {
171 fn quote(&self) -> proc_macro2::TokenStream;
172}
173
174impl Quote for tzif::MaybeNamedTimeZone {
175 fn quote(&self) -> proc_macro2::TokenStream {
176 let tzif::MaybeNamedTimeZone { ref name, ref tz } = *self;
177 let tzif::TimeZone {
178 version,
179 checksum,
180 ref designations,
181 ref posix_tz,
182 ref types,
183 ref transitions,
184 } = *tz;
185 let name = name.as_ref().unwrap().quote();
187 let designations = designations.iter().map(Quote::quote);
188 let posix_tz = posix_tz
189 .as_ref()
190 .map(|tz| {
191 let tz = tz.quote();
192 quote!(Some(#tz))
193 })
194 .unwrap_or_else(|| quote!(None));
195 let types = types.iter().map(tzif::LocalTimeType::quote);
196 let transitions = transitions.quote();
197 quote! {{
198 static __TZ_DESIGNATIONS: &[jiff::__jcore::tz::Abbreviation] = &[#(#designations),*];
199 static __TZ_INTERNAL: jiff::__jcore::tz::tzif::MaybeNamedTimeZone =
200 jiff::__jcore::tz::tzif::MaybeNamedTimeZone {
201 name: Some(#name),
202 tz: jiff::__jcore::tz::tzif::TimeZone {
203 version: #version,
204 checksum: #checksum,
205 designations: jiff::__jcore::util::MaybeStaticSlice::statik(__TZ_DESIGNATIONS),
206 posix_tz: #posix_tz,
207 types: jiff::__jcore::util::MaybeStaticSlice::statik(&[#(#types),*]),
208 transitions: #transitions,
209 },
210 };
211 static TZ: jiff::tz::TimeZone =
212 jiff::tz::TimeZone::__internal_from_tzif(&__TZ_INTERNAL);
213 unsafe { TZ.copy() }
234 }}
235 }
236}
237
238impl Quote for tzif::Transitions {
239 fn quote(&self) -> proc_macro2::TokenStream {
240 let tzif::Transitions {
241 ref timestamps,
242 ref civil_starts,
243 ref civil_ends,
244 ref infos,
245 } = *self;
246 let timestamps = timestamps.iter().map(tzif::Timestamp::quote);
247 let civil_starts = civil_starts.iter().map(tzif::DateTime::quote);
248 let civil_ends = civil_ends.iter().map(tzif::DateTime::quote);
249 let infos = infos.iter().map(tzif::TransitionInfo::quote);
250 quote! {
251 jiff::__jcore::tz::tzif::Transitions {
252 timestamps: jiff::__jcore::util::MaybeStaticSlice::statik(&[#(#timestamps),*]),
253 civil_starts: jiff::__jcore::util::MaybeStaticSlice::statik(&[#(#civil_starts),*]),
254 civil_ends: jiff::__jcore::util::MaybeStaticSlice::statik(&[#(#civil_ends),*]),
255 infos: jiff::__jcore::util::MaybeStaticSlice::statik(&[#(#infos),*]),
256 }
257 }
258 }
259}
260
261impl Quote for tzif::LocalTimeType {
262 fn quote(&self) -> proc_macro2::TokenStream {
263 let tzif::LocalTimeType { offset, dst, designation, indicator } =
264 *self;
265 let offset = offset.quote();
266 let dst = dst.quote();
267 let indicator = indicator.quote();
268 quote! {
269 jiff::__jcore::tz::tzif::LocalTimeType {
270 offset: #offset,
271 dst: #dst,
272 designation: #designation,
273 indicator: #indicator,
274 }
275 }
276 }
277}
278
279impl Quote for tzif::Indicator {
280 fn quote(&self) -> proc_macro2::TokenStream {
281 match *self {
282 tzif::Indicator::LocalWall => quote! {
283 jiff::__jcore::tz::tzif::Indicator::LocalWall
284 },
285 tzif::Indicator::LocalStandard => quote! {
286 jiff::__jcore::tz::tzif::Indicator::LocalStandard
287 },
288 tzif::Indicator::UTStandard => quote! {
289 jiff::__jcore::tz::tzif::Indicator::UTStandard
290 },
291 }
292 }
293}
294
295impl Quote for tzif::TransitionInfo {
296 fn quote(&self) -> proc_macro2::TokenStream {
297 let tzif::TransitionInfo { type_index, kind } = *self;
298 let kind = kind.quote();
299 quote! {
300 jiff::__jcore::tz::tzif::TransitionInfo {
301 type_index: #type_index,
302 kind: #kind,
303 }
304 }
305 }
306}
307
308impl Quote for tzif::TransitionKind {
309 fn quote(&self) -> proc_macro2::TokenStream {
310 match *self {
311 tzif::TransitionKind::Unambiguous => quote! {
312 jiff::__jcore::tz::tzif::TransitionKind::Unambiguous
313 },
314 tzif::TransitionKind::Gap => quote! {
315 jiff::__jcore::tz::tzif::TransitionKind::Gap
316 },
317 tzif::TransitionKind::Fold => quote! {
318 jiff::__jcore::tz::tzif::TransitionKind::Fold
319 },
320 }
321 }
322}
323
324impl Quote for tzif::DateTime {
325 fn quote(&self) -> proc_macro2::TokenStream {
326 let year = self.year();
327 let month = self.month();
328 let day = self.day();
329 let hour = self.hour();
330 let minute = self.minute();
331 let second = self.second();
332 quote! {
333 jiff::__jcore::tz::tzif::DateTime::new(jiff::__jcore::civil::datetime(
334 #year,
335 #month,
336 #day,
337 #hour,
338 #minute,
339 #second,
340 0
341 ))
342 }
343 }
344}
345
346impl Quote for posix::TimeZone {
347 fn quote(&self) -> proc_macro2::TokenStream {
348 let posix::TimeZone { ref std_abbrev, ref std_offset, ref dst } =
349 *self;
350 let std_abbrev = std_abbrev.quote();
351 let std_offset = std_offset.quote();
352 let dst = dst
353 .as_ref()
354 .map(|dst| {
355 let dst = dst.quote();
356 quote!(Some(#dst))
357 })
358 .unwrap_or_else(|| quote!(None));
359 quote! {
360 jiff::__jcore::tz::posix::TimeZone {
361 std_abbrev: #std_abbrev,
362 std_offset: #std_offset,
363 dst: #dst,
364 }
365 }
366 }
367}
368
369impl Quote for posix::Dst {
370 fn quote(&self) -> proc_macro2::TokenStream {
371 let posix::Dst { ref abbrev, ref offset, ref rule } = *self;
372 let abbrev = abbrev.quote();
373 let offset = offset.quote();
374 let rule = rule.quote();
375 quote! {
376 jiff::__jcore::tz::posix::Dst {
377 abbrev: #abbrev,
378 offset: #offset,
379 rule: #rule,
380 }
381 }
382 }
383}
384
385impl Quote for posix::Rule {
386 fn quote(&self) -> proc_macro2::TokenStream {
387 let start = self.start.quote();
388 let end = self.end.quote();
389 quote! {
390 jiff::__jcore::tz::posix::Rule { start: #start, end: #end }
391 }
392 }
393}
394
395impl Quote for posix::DayTime {
396 fn quote(&self) -> proc_macro2::TokenStream {
397 let posix::DayTime { ref date, ref time } = *self;
398 let date = date.quote();
399 let time = time.quote();
400 quote! {
401 jiff::__jcore::tz::posix::DayTime { date: #date, time: #time }
402 }
403 }
404}
405
406impl Quote for posix::Day {
407 fn quote(&self) -> proc_macro2::TokenStream {
408 match *self {
409 posix::Day::JulianOne(day) => quote! {
410 jiff::__jcore::tz::posix::Day::JulianOne(#day)
411 },
412 posix::Day::JulianZero(day) => quote! {
413 jiff::__jcore::tz::posix::Day::JulianZero(#day)
414 },
415 posix::Day::WeekdayOfMonth { month, week, weekday } => {
416 let weekday = weekday.quote();
417 quote! {
418 jiff::__jcore::tz::posix::Day::WeekdayOfMonth {
419 month: #month,
420 week: #week,
421 weekday: #weekday,
422 }
423 }
424 }
425 }
426 }
427}
428
429impl Quote for jcore::civil::Weekday {
430 fn quote(&self) -> proc_macro2::TokenStream {
431 use jcore::civil::Weekday::*;
432 match *self {
433 Sunday => quote!(jiff::__jcore::civil::Weekday::Sunday),
434 Monday => quote!(jiff::__jcore::civil::Weekday::Monday),
435 Tuesday => quote!(jiff::__jcore::civil::Weekday::Tuesday),
436 Wednesday => quote!(jiff::__jcore::civil::Weekday::Wednesday),
437 Thursday => quote!(jiff::__jcore::civil::Weekday::Thursday),
438 Friday => quote!(jiff::__jcore::civil::Weekday::Friday),
439 Saturday => quote!(jiff::__jcore::civil::Weekday::Saturday),
440 }
441 }
442}
443
444impl Quote for jcore::tz::posix::TransitionCivilTime {
445 fn quote(&self) -> proc_macro2::TokenStream {
446 let posix::TransitionCivilTime { second } = *self;
447 quote! {
448 jiff::__jcore::tz::posix::TransitionCivilTime { second: #second }
449 }
450 }
451}
452
453impl Quote for tzif::Timestamp {
454 fn quote(&self) -> proc_macro2::TokenStream {
455 let second = self.as_second();
456 quote! {
457 jiff::__jcore::tz::tzif::Timestamp::new(
458 jiff::__jcore::Timestamp::constant(#second, 0),
459 )
460 }
461 }
462}
463
464impl Quote for jcore::tz::Offset {
465 fn quote(&self) -> proc_macro2::TokenStream {
466 let seconds = self.seconds();
467 quote! {
468 jiff::__jcore::tz::Offset::constant_seconds(#seconds)
469 }
470 }
471}
472
473impl Quote for jcore::tz::Dst {
474 fn quote(&self) -> proc_macro2::TokenStream {
475 match *self {
476 jcore::tz::Dst::Yes => quote! { jiff::__jcore::tz::Dst::Yes },
477 jcore::tz::Dst::No => quote! { jiff::__jcore::tz::Dst::No },
478 }
479 }
480}
481
482impl<const N: usize> Quote for jcore::util::SmallStr<N> {
483 fn quote(&self) -> proc_macro2::TokenStream {
484 let s = self.as_str();
485 quote! {
486 jiff::__jcore::util::SmallStr::statik(#s)
487 }
488 }
489}
490
491impl<T: Quote + 'static> Quote for jcore::util::MaybeStaticSlice<T> {
492 fn quote(&self) -> proc_macro2::TokenStream {
493 let slice = self.as_slice().iter().map(Quote::quote);
494 quote! {{
495 jiff::__jcore::util::MaybeStaticSlice::statik(&[#(#slice),*])
496 }}
497 }
498}