Coverage Report

Created: 2026-07-14 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.31/src/shared/mod.rs
Line
Count
Source
1
/*!
2
Defines data types shared between `jiff` and `jiff-static`.
3
4
While this module exposes types that can be imported outside of `jiff` itself,
5
there are *no* semver guarantees provided. That is, this module is _not_ part
6
of Jiff's public API. The only guarantee of compatibility that is provided
7
is that `jiff-static x.y.z` works with one and only one version of Jiff,
8
corresponding to `jiff x.y.z` (i.e., the same version number).
9
10
# Design
11
12
This module is really accomplishing two different things at the same time.
13
14
Firstly, it is a way to provide types that can be used to construct a static
15
`TimeZone`. The proc macros in `jiff-static` generate code using these
16
types (and a few routines).
17
18
Secondly, it provides a way to parse TZif data without `jiff-static`
19
depending on `jiff` via a Cargo dependency. This actually requires copying
20
the code in this module (which is why it is kinda sectioned off from the rest
21
of jiff) into the `jiff-static` crate. This can be done automatically with
22
`jiff-cli`:
23
24
```text
25
jiff-cli generate shared
26
```
27
28
The copying of code is pretty unfortunate, because it means both crates have to
29
compile it. However, the alternatives aren't great either.
30
31
One alternative is to have `jiff-static` explicitly depend on `jiff` in its
32
`Cargo.toml`. Then Jiff could expose the parsing routines, as it does here,
33
and `jiff-static` could use them directly. Unfortunately, this means that
34
`jiff` cannot depend on `jiff-static`. And that in turn means that `jiff`
35
cannot re-export the macros. Users will need to explicitly depend on and use
36
`jiff-static`. Moreover, this could result in some potential surprises
37
since `jiff-static` will need to have an `=x.y.z` dependency on Jiff for
38
compatibility reasons. That in turn means that the version of Jiff actually
39
used is not determine by the user's `jiff = "x.y.z"` line, but rather by the
40
user's `jiff-static = "x'.y'.z'"` line. This is overall annoying and not a
41
good user experience. Plus, it inverts the typical relationship between crates
42
and their proc macros (e.g., `serde` and `serde_derive`) and thus could result
43
in other unanticipated surprises.
44
45
Another obvious alternative is to split this code out into a separate crate
46
that both `jiff` and `jiff-static` depend on. However, the API exposed in
47
this module does not provide a coherent user experience. It would either need a
48
ton of work to turn it into a coherent user experience or it would need to be
49
published as a `jiff-internal-use-only` crate that I find to be very annoying
50
and confusing. Moreover, a separate crate introduces a new semver boundary
51
beneath Jiff. I've found these sorts of things to overall increase maintenance
52
burden (see ripgrep and regex for cases where I did this).
53
54
I overall decided that the least bad choice was to copy a little code (under
55
2,000 source lines of code at present I believe). Since the copy is managed
56
automatically via `jiff-cli generate shared`, we remove the downside of the
57
code getting out of sync. The only downside is extra compile time. Since I
58
generally only expect `jiff-static` to be used in niche circumstances, I
59
prefer this trade-off over the other choices.
60
61
More context on how I arrived at this design can be found here:
62
<https://github.com/BurntSushi/jiff/issues/256>
63
64
# Particulars
65
66
When this code is copied to `jiff-static`, the following transformations are
67
done:
68
69
* A header is added to indicate that the copied file is auto-generated.
70
* All `#[cfg(feature = "alloc")]` annotations are removed. The `jiff-static`
71
  proc macro always runs in a context where the standard library is available.
72
* Any code between `// only-jiff-start` and `// only-jiff-end` comments is
73
  removed. Nesting isn't supported.
74
* All `#[cfg(feature = "defmt")]` annotations and gated items are removed. The
75
  `jiff-static` proc macro only needs these types internally for parsing and
76
  code generation.
77
78
Otherwise, this module is specifically organized in a way that doesn't rely on
79
any other part of Jiff. The one exception are routines to convert from these
80
exposed types to other internal types inside of Jiff. This is necessary for
81
building a static `TimeZone`. But these conversion routines are removed when
82
this module is copied to `jiff-static`.
83
*/
84
85
/// An alias for TZif data whose backing storage has a `'static` lifetime.
86
// only-jiff-start
87
pub type TzifStatic = Tzif<
88
    &'static str,
89
    &'static str,
90
    &'static [TzifLocalTimeType],
91
    &'static [i64],
92
    &'static [TzifDateTime],
93
    &'static [TzifDateTime],
94
    &'static [TzifTransitionInfo],
95
>;
96
// only-jiff-end
97
98
/// An alias for TZif data whose backing storage is on the heap.
99
#[cfg(feature = "alloc")]
100
pub type TzifOwned = Tzif<
101
    alloc::string::String,
102
    self::util::array_str::Abbreviation,
103
    alloc::vec::Vec<TzifLocalTimeType>,
104
    alloc::vec::Vec<i64>,
105
    alloc::vec::Vec<TzifDateTime>,
106
    alloc::vec::Vec<TzifDateTime>,
107
    alloc::vec::Vec<TzifTransitionInfo>,
108
>;
109
110
/// An alias for TZif transition data whose backing storage is on the heap.
111
#[cfg(feature = "alloc")]
112
pub type TzifTransitionsOwned = TzifTransitions<
113
    alloc::vec::Vec<i64>,
114
    alloc::vec::Vec<TzifDateTime>,
115
    alloc::vec::Vec<TzifDateTime>,
116
    alloc::vec::Vec<TzifTransitionInfo>,
117
>;
118
119
#[derive(Clone, Debug)]
120
pub struct Tzif<STR, ABBREV, TYPES, TIMESTAMPS, STARTS, ENDS, INFOS> {
121
    pub fixed: TzifFixed<STR, ABBREV>,
122
    pub types: TYPES,
123
    pub transitions: TzifTransitions<TIMESTAMPS, STARTS, ENDS, INFOS>,
124
}
125
126
#[derive(Clone, Debug)]
127
pub struct TzifFixed<STR, ABBREV> {
128
    pub name: Option<STR>,
129
    /// An ASCII byte corresponding to the version number. So, 0x50 is '2'.
130
    ///
131
    /// This is unused. It's only used in `test` compilation for emitting
132
    /// diagnostic data about TZif files. If we really need to use this, we
133
    /// should probably just convert it to an actual integer.
134
    pub version: u8,
135
    pub checksum: u32,
136
    pub designations: STR,
137
    pub posix_tz: Option<PosixTimeZone<ABBREV>>,
138
}
139
140
#[derive(Clone, Copy, Debug)]
141
pub struct TzifLocalTimeType {
142
    pub offset: i32,
143
    pub is_dst: bool,
144
    pub designation: (u8, u8), // inclusive..exclusive
145
    pub indicator: TzifIndicator,
146
}
147
148
/// This enum corresponds to the possible indicator values for standard/wall
149
/// and UT/local.
150
///
151
/// Note that UT+Wall is not allowed.
152
///
153
/// I honestly have no earthly clue what they mean. I've read the section about
154
/// them in RFC 8536 several times and I can't make sense of it. I've even
155
/// looked at data files that have these set and still can't make sense of
156
/// them. I've even looked at what other datetime libraries do with these, and
157
/// they all seem to just ignore them. Like, WTF. I've spent the last couple
158
/// months of my life steeped in time, and I just cannot figure this out. Am I
159
/// just dumb?
160
///
161
/// Anyway, we parse them, but otherwise ignore them because that's what all
162
/// the cool kids do.
163
///
164
/// The default is `LocalWall`, which also occurs when no indicators are
165
/// present.
166
///
167
/// I tried again and still don't get it. Here's a dump for `Pacific/Honolulu`:
168
///
169
/// ```text
170
/// $ ./scripts/jiff-debug tzif /usr/share/zoneinfo/Pacific/Honolulu
171
/// TIME ZONE NAME
172
///   /usr/share/zoneinfo/Pacific/Honolulu
173
/// LOCAL TIME TYPES
174
///   000: offset=-10:31:26, is_dst=false, designation=LMT, indicator=local/wall
175
///   001: offset=-10:30, is_dst=false, designation=HST, indicator=local/wall
176
///   002: offset=-09:30, is_dst=true, designation=HDT, indicator=local/wall
177
///   003: offset=-09:30, is_dst=true, designation=HWT, indicator=local/wall
178
///   004: offset=-09:30, is_dst=true, designation=HPT, indicator=ut/std
179
///   005: offset=-10, is_dst=false, designation=HST, indicator=local/wall
180
/// TRANSITIONS
181
///   0000: -9999-01-02T01:59:59 :: -377705023201 :: type=0, -10:31:26, is_dst=false, LMT, local/wall
182
///   0001: 1896-01-13T22:31:26 :: -2334101314 :: type=1, -10:30, is_dst=false, HST, local/wall
183
///   0002: 1933-04-30T12:30:00 :: -1157283000 :: type=2, -09:30, is_dst=true, HDT, local/wall
184
///   0003: 1933-05-21T21:30:00 :: -1155436200 :: type=1, -10:30, is_dst=false, HST, local/wall
185
///   0004: 1942-02-09T12:30:00 :: -880198200 :: type=3, -09:30, is_dst=true, HWT, local/wall
186
///   0005: 1945-08-14T23:00:00 :: -769395600 :: type=4, -09:30, is_dst=true, HPT, ut/std
187
///   0006: 1945-09-30T11:30:00 :: -765376200 :: type=1, -10:30, is_dst=false, HST, local/wall
188
///   0007: 1947-06-08T12:30:00 :: -712150200 :: type=5, -10, is_dst=false, HST, local/wall
189
/// POSIX TIME ZONE STRING
190
///   HST10
191
/// ```
192
///
193
/// See how type 004 has a ut/std indicator? What the fuck does that mean?
194
/// All transitions are defined in terms of UTC. I confirmed this with `zdump`:
195
///
196
/// ```text
197
/// $ zdump -v Pacific/Honolulu | rg 1945
198
/// Pacific/Honolulu  Tue Aug 14 22:59:59 1945 UT = Tue Aug 14 13:29:59 1945 HWT isdst=1 gmtoff=-34200
199
/// Pacific/Honolulu  Tue Aug 14 23:00:00 1945 UT = Tue Aug 14 13:30:00 1945 HPT isdst=1 gmtoff=-34200
200
/// Pacific/Honolulu  Sun Sep 30 11:29:59 1945 UT = Sun Sep 30 01:59:59 1945 HPT isdst=1 gmtoff=-34200
201
/// Pacific/Honolulu  Sun Sep 30 11:30:00 1945 UT = Sun Sep 30 01:00:00 1945 HST isdst=0 gmtoff=-37800
202
/// ```
203
///
204
/// The times match up. All of them. The indicators don't seem to make a
205
/// difference. I'm clearly missing something.
206
#[derive(Clone, Copy, Debug)]
207
pub enum TzifIndicator {
208
    LocalWall,
209
    LocalStandard,
210
    UTStandard,
211
}
212
213
/// The set of transitions in TZif data, laid out in column orientation.
214
///
215
/// The column orientation is used to make TZ lookups faster. Specifically,
216
/// for finding an offset for a timestamp, we do a binary search on
217
/// `timestamps`. For finding an offset for a local datetime, we do a binary
218
/// search on `civil_starts`. By making these two distinct sequences with
219
/// nothing else in them, we make them as small as possible and thus improve
220
/// cache locality.
221
///
222
/// All sequences in this type are in correspondence with one another. They
223
/// are all guaranteed to have the same length.
224
#[derive(Clone, Debug)]
225
pub struct TzifTransitions<TIMESTAMPS, STARTS, ENDS, INFOS> {
226
    /// The timestamp at which this transition begins.
227
    pub timestamps: TIMESTAMPS,
228
    /// The wall clock time for when a transition begins.
229
    pub civil_starts: STARTS,
230
    /// The wall clock time for when a transition ends.
231
    ///
232
    /// This is only non-zero when the transition kind is a gap or a fold.
233
    pub civil_ends: ENDS,
234
    /// Any other relevant data about a transition, such as its local type
235
    /// index and the transition kind.
236
    pub infos: INFOS,
237
}
238
239
/// TZif transition info beyond the timestamp and civil datetime.
240
///
241
/// For example, this contains a transition's "local type index," which in
242
/// turn gives access to the offset (among other metadata) for that transition.
243
#[derive(Clone, Copy, Debug)]
244
pub struct TzifTransitionInfo {
245
    /// The index into the sequence of local time type records. This is what
246
    /// provides the correct offset (from UTC) that is active beginning at
247
    /// this transition.
248
    pub type_index: u8,
249
    /// The boundary condition for quickly determining if a given wall clock
250
    /// time is ambiguous (i.e., falls in a gap or a fold).
251
    pub kind: TzifTransitionKind,
252
}
253
254
/// The kind of a transition.
255
///
256
/// This is used when trying to determine the offset for a local datetime. It
257
/// indicates how the corresponding civil datetimes in `civil_starts` and
258
/// `civil_ends` should be interpreted. That is, there are three possible
259
/// cases:
260
///
261
/// 1. The offset of this transition is equivalent to the offset of the
262
/// previous transition. That means there are no ambiguous civil datetimes
263
/// between the transitions. This can occur, e.g., when the time zone
264
/// abbreviation changes.
265
/// 2. The offset of the transition is greater than the offset of the previous
266
/// transition. That means there is a "gap" in local time between the
267
/// transitions. This typically corresponds to entering daylight saving time.
268
/// It is usually, but not always, 1 hour.
269
/// 3. The offset of the transition is less than the offset of the previous
270
/// transition. That means there is a "fold" in local time where time is
271
/// repeated. This typically corresponds to leaving daylight saving time. It
272
/// is usually, but not always, 1 hour.
273
///
274
/// # More explanation
275
///
276
/// This, when combined with `civil_starts` and `civil_ends` in
277
/// `TzifTransitions`, explicitly represents ambiguous wall clock times that
278
/// occur at the boundaries of transitions.
279
///
280
/// The start of the wall clock time is always the earlier possible wall clock
281
/// time that could occur with this transition's corresponding offset. For a
282
/// gap, it's the previous transition's offset. For a fold, it's the current
283
/// transition's offset.
284
///
285
/// For example, DST for `America/New_York` began on `2024-03-10T07:00:00+00`.
286
/// The offset prior to this instant in time is `-05`, corresponding
287
/// to standard time (EST). Thus, in wall clock time, DST began at
288
/// `2024-03-10T02:00:00`. And since this is a DST transition that jumps ahead
289
/// an hour, the start of DST also corresponds to the start of a gap. That is,
290
/// the times `02:00:00` through `02:59:59` never appear on a clock for this
291
/// hour. The question is thus: which offset should we apply to `02:00:00`?
292
/// We could apply the offset from the earlier transition `-05` and get
293
/// `2024-03-10T01:00:00-05` (that's `2024-03-10T06:00:00+00`), or we could
294
/// apply the offset from the later transition `-04` and get
295
/// `2024-03-10T03:00:00-04` (that's `2024-03-10T07:00:00+00`).
296
///
297
/// So in the above, we would have a `Gap` variant where `start` (inclusive) is
298
/// `2024-03-10T02:00:00` and `end` (exclusive) is `2024-03-10T03:00:00`.
299
///
300
/// The fold case is the same idea, but where the same time is repeated.
301
/// For example, in `America/New_York`, standard time began on
302
/// `2024-11-03T06:00:00+00`. The offset prior to this instant in time
303
/// is `-04`, corresponding to DST (EDT). Thus, in wall clock time, DST
304
/// ended at `2024-11-03T02:00:00`. However, since this is a fold, the
305
/// actual set of ambiguous times begins at `2024-11-03T01:00:00` and
306
/// ends at `2024-11-03T01:59:59.999999999`. That is, the wall clock time
307
/// `2024-11-03T02:00:00` is unambiguous.
308
///
309
/// So in the fold case above, we would have a `Fold` variant where
310
/// `start` (inclusive) is `2024-11-03T01:00:00` and `end` (exclusive) is
311
/// `2024-11-03T02:00:00`.
312
///
313
/// Since this gets bundled in with the sorted sequence of transitions, we'll
314
/// use the "start" time in all three cases as our target of binary search.
315
/// Once we land on a transition, we'll know our given wall clock time is
316
/// greater than or equal to its start wall clock time. At that point, to
317
/// determine if there is ambiguity, we merely need to determine if the given
318
/// wall clock time is less than the corresponding `end` time. If it is, then
319
/// it falls in a gap or fold. Otherwise, it's unambiguous.
320
///
321
/// Note that we could compute these datetime values while searching for the
322
/// correct transition, but there's a fair bit of math involved in going
323
/// between timestamps (which is what TZif gives us) and calendar datetimes
324
/// (which is what we're given as input). It is also necessary that we offset
325
/// the timestamp given in TZif at some point, since it is in UTC and the
326
/// datetime given is in wall clock time. So I decided it would be worth
327
/// pre-computing what we need in terms of what the input is. This way, we
328
/// don't need to do any conversions, or indeed, any arithmetic at all, for
329
/// time zone lookups. We *could* store these as transitions, but then the
330
/// input datetime would need to be converted to a timestamp before searching
331
/// the transitions.
332
#[derive(Clone, Copy, Debug)]
333
pub enum TzifTransitionKind {
334
    /// This transition cannot possibly lead to an unambiguous offset because
335
    /// its offset is equivalent to the offset of the previous transition.
336
    ///
337
    /// Has an entry in `civil_starts`, but corresponding entry in `civil_ends`
338
    /// is always zeroes (i.e., meaningless).
339
    Unambiguous,
340
    /// This occurs when this transition's offset is strictly greater than the
341
    /// previous transition's offset. This effectively results in a "gap" of
342
    /// time equal to the difference in the offsets between the two
343
    /// transitions.
344
    ///
345
    /// Has an entry in `civil_starts` for when the gap starts (inclusive) in
346
    /// local time. Also has an entry in `civil_ends` for when the fold ends
347
    /// (exclusive) in local time.
348
    Gap,
349
    /// This occurs when this transition's offset is strictly less than the
350
    /// previous transition's offset. This results in a "fold" of time where
351
    /// the two transitions have an overlap where it is ambiguous which one
352
    /// applies given a wall clock time. In effect, a span of time equal to the
353
    /// difference in the offsets is repeated.
354
    ///
355
    /// Has an entry in `civil_starts` for when the fold starts (inclusive) in
356
    /// local time. Also has an entry in `civil_ends` for when the fold ends
357
    /// (exclusive) in local time.
358
    Fold,
359
}
360
361
/// The representation we use to represent a civil datetime.
362
///
363
/// We don't use `shared::util::itime::IDateTime` here because we specifically
364
/// do not need to represent fractional seconds. This lets us easily represent
365
/// what we need in 8 bytes instead of the 12 bytes used by `IDateTime`.
366
///
367
/// Moreover, we pack the fields into a single `i64` to make comparisons
368
/// extremely cheap. This is especially useful since we do a binary search on
369
/// `&[TzifDateTime]` when doing a TZ lookup for a civil datetime.
370
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
371
pub struct TzifDateTime {
372
    bits: i64,
373
}
374
375
impl TzifDateTime {
376
    pub const ZERO: TzifDateTime = TzifDateTime::new(0, 0, 0, 0, 0, 0);
377
    const MIN: TzifDateTime = TzifDateTime::new(-9999, 1, 1, 0, 0, 0);
378
379
0
    pub const fn new(
380
0
        year: i16,
381
0
        month: i8,
382
0
        day: i8,
383
0
        hour: i8,
384
0
        minute: i8,
385
0
        second: i8,
386
0
    ) -> TzifDateTime {
387
0
        let mut bits = (year as u64) << 48;
388
0
        bits |= (month as u64) << 40;
389
0
        bits |= (day as u64) << 32;
390
0
        bits |= (hour as u64) << 24;
391
0
        bits |= (minute as u64) << 16;
392
0
        bits |= (second as u64) << 8;
393
        // The least significant 8 bits remain 0.
394
0
        TzifDateTime { bits: bits as i64 }
395
0
    }
396
397
0
    pub const fn year(self) -> i16 {
398
0
        (self.bits as u64 >> 48) as u16 as i16
399
0
    }
400
401
0
    pub const fn month(self) -> i8 {
402
0
        (self.bits as u64 >> 40) as u8 as i8
403
0
    }
404
405
0
    pub const fn day(self) -> i8 {
406
0
        (self.bits as u64 >> 32) as u8 as i8
407
0
    }
408
409
0
    pub const fn hour(self) -> i8 {
410
0
        (self.bits as u64 >> 24) as u8 as i8
411
0
    }
412
413
0
    pub const fn minute(self) -> i8 {
414
0
        (self.bits as u64 >> 16) as u8 as i8
415
0
    }
416
417
0
    pub const fn second(self) -> i8 {
418
0
        (self.bits as u64 >> 8) as u8 as i8
419
0
    }
420
}
421
422
impl core::fmt::Debug for TzifDateTime {
423
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
424
0
        if !f.alternate() {
425
0
            f.debug_struct("TzifDateTime").field("bits", &self.bits).finish()
426
        } else {
427
0
            f.debug_tuple("TzifDateTime")
428
0
                .field(&format_args!(
429
0
                    "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
430
0
                    self.year(),
431
0
                    self.month(),
432
0
                    self.day(),
433
0
                    self.hour(),
434
0
                    self.minute(),
435
0
                    self.second(),
436
0
                ))
437
0
                .finish()
438
        }
439
0
    }
440
}
441
442
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
443
pub struct PosixTimeZone<ABBREV> {
444
    pub std_abbrev: ABBREV,
445
    pub std_offset: PosixOffset,
446
    pub dst: Option<PosixDst<ABBREV>>,
447
}
448
449
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
450
pub struct PosixDst<ABBREV> {
451
    pub abbrev: ABBREV,
452
    pub offset: PosixOffset,
453
    pub rule: PosixRule,
454
}
455
456
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
457
pub struct PosixRule {
458
    pub start: PosixDayTime,
459
    pub end: PosixDayTime,
460
}
461
462
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
463
pub struct PosixDayTime {
464
    pub date: PosixDay,
465
    pub time: PosixTime,
466
}
467
468
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
469
pub enum PosixDay {
470
    /// Julian day in a year, no counting for leap days.
471
    ///
472
    /// Valid range is `1..=365`.
473
    JulianOne(i16),
474
    /// Julian day in a year, counting for leap days.
475
    ///
476
    /// Valid range is `0..=365`.
477
    JulianZero(i16),
478
    /// The nth weekday of a month.
479
    WeekdayOfMonth {
480
        /// The month.
481
        ///
482
        /// Valid range is: `1..=12`.
483
        month: i8,
484
        /// The week.
485
        ///
486
        /// Valid range is `1..=5`.
487
        ///
488
        /// One interesting thing to note here (or my interpretation anyway),
489
        /// is that a week of `4` means the "4th weekday in a month" where as
490
        /// a week of `5` means the "last weekday in a month, even if it's the
491
        /// 4th weekday."
492
        week: i8,
493
        /// The weekday.
494
        ///
495
        /// Valid range is `0..=6`, with `0` corresponding to Sunday.
496
        weekday: i8,
497
    },
498
}
499
500
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
501
pub struct PosixTime {
502
    pub second: i32,
503
}
504
505
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
506
pub struct PosixOffset {
507
    pub second: i32,
508
}
509
510
// only-jiff-start
511
impl TzifStatic {
512
0
    pub const fn into_jiff(self) -> crate::tz::tzif::TzifStatic {
513
0
        crate::tz::tzif::TzifStatic::from_shared_const(self)
514
0
    }
515
}
516
// only-jiff-end
517
518
// only-jiff-start
519
impl PosixTimeZone<&'static str> {
520
0
    pub const fn into_jiff(self) -> crate::tz::posix::PosixTimeZoneStatic {
521
0
        crate::tz::posix::PosixTimeZone::from_shared_const(self)
522
0
    }
523
}
524
// only-jiff-end
525
526
// Does not require `alloc`, but is only used when `alloc` is enabled.
527
#[cfg(feature = "alloc")]
528
pub(crate) mod crc32;
529
pub(crate) mod posix;
530
#[cfg(feature = "alloc")]
531
pub(crate) mod tzif;
532
pub(crate) mod util;