Coverage Report

Created: 2026-01-16 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.18/src/shared/tzif.rs
Line
Count
Source
1
use alloc::{string::String, vec};
2
3
use super::{
4
    util::{
5
        array_str::Abbreviation,
6
        itime::{IOffset, ITimestamp},
7
    },
8
    PosixTimeZone, TzifDateTime, TzifFixed, TzifIndicator, TzifLocalTimeType,
9
    TzifOwned, TzifTransitionInfo, TzifTransitionKind, TzifTransitions,
10
    TzifTransitionsOwned,
11
};
12
13
// These are Jiff min and max timestamp (in seconds) values.
14
//
15
// The TZif parser will clamp timestamps to this range. It's
16
// not ideal, but Jiff can't handle values outside of this range
17
// and completely refusing to use TZif data with pathological
18
// timestamps in typically irrelevant transitions is bad juju.
19
//
20
// Ref: https://github.com/BurntSushi/jiff/issues/163
21
// Ref: https://github.com/BurntSushi/jiff/pull/164
22
const TIMESTAMP_MIN: i64 = -377705023201;
23
const TIMESTAMP_MAX: i64 = 253402207200;
24
25
// Similarly for offsets, although in this case, if we find
26
// an offset outside of this range, we do actually error. This
27
// is because it could result in true incorrect datetimes for
28
// actual transitions.
29
//
30
// But our supported offset range is `-25:59:59..=+25:59:59`.
31
// There's no real time zone with offsets even close to those
32
// boundaries.
33
//
34
// If there is pathological data that we should ignore, then
35
// we should wait for a real bug report in order to determine
36
// the right way to ignore/clamp it.
37
const OFFSET_MIN: i32 = -93599;
38
const OFFSET_MAX: i32 = 93599;
39
40
// When fattening TZif data, this is the year to go up to.
41
//
42
// This year was chosen because it's what the "fat" TZif data generated
43
// by `zic` uses.
44
const FATTEN_UP_TO_YEAR: i16 = 2038;
45
46
// This is a "sanity" limit on the maximum number of transitions we'll
47
// add to TZif data when fattening them up.
48
//
49
// This is mostly just a defense-in-depth limit to avoid weird cases
50
// where a pathological POSIX time zone could be defined to create
51
// many transitions. It's not clear that this is actually possible,
52
// but I felt a little uneasy doing unbounded work that isn't linearly
53
// proportional to the input data. So, this limit is put into place for
54
// reasons of "good sense."
55
//
56
// For "normal" cases, there should be at most two transitions per
57
// year. So this limit permits 300/2=150 years of transition data.
58
// (Although we won't go above `FATTEN_UP_TO_YEAR`. See above.)
59
const FATTEN_MAX_TRANSITIONS: usize = 300;
60
61
impl TzifOwned {
62
    /// Parses the given data as a TZif formatted file.
63
    ///
64
    /// The name given is attached to the `Tzif` value returned, but is
65
    /// otherwise not significant.
66
    ///
67
    /// If the given data is not recognized to be valid TZif, then an error is
68
    /// returned.
69
    ///
70
    /// In general, callers may assume that it is safe to pass arbitrary or
71
    /// even untrusted data to this function and count on it not panicking
72
    /// or using resources that aren't limited to a small constant factor of
73
    /// the size of the data itself. That is, callers can reliably limit the
74
    /// resources used by limiting the size of the data given to this parse
75
    /// function.
76
0
    pub(crate) fn parse(
77
0
        name: Option<String>,
78
0
        bytes: &[u8],
79
0
    ) -> Result<TzifOwned, TzifError> {
80
0
        let original = bytes;
81
0
        let name = name.into();
82
0
        let (header32, rest) =
83
0
            Header::parse(4, bytes).map_err(TzifErrorKind::Header32)?;
84
0
        let (mut tzif, rest) = if header32.version == 0 {
85
0
            TzifOwned::parse32(name, header32, rest)?
86
        } else {
87
0
            TzifOwned::parse64(name, header32, rest)?
88
        };
89
0
        tzif.fatten();
90
        // This should come after fattening, because fattening may add new
91
        // transitions and we want to add civil datetimes to those.
92
0
        tzif.add_civil_datetimes_to_transitions();
93
0
        tzif.verify_posix_time_zone_consistency()?;
94
        // Compute the checksum using the entire contents of the TZif data.
95
0
        let tzif_raw_len = (rest.as_ptr() as usize)
96
0
            .checked_sub(original.as_ptr() as usize)
97
0
            .unwrap();
98
0
        let tzif_raw_bytes = &original[..tzif_raw_len];
99
0
        tzif.fixed.checksum = super::crc32::sum(tzif_raw_bytes);
100
101
        // Shrink all of our allocs so we don't keep excess capacity around.
102
0
        tzif.fixed.designations.shrink_to_fit();
103
0
        tzif.types.shrink_to_fit();
104
0
        tzif.transitions.timestamps.shrink_to_fit();
105
0
        tzif.transitions.civil_starts.shrink_to_fit();
106
0
        tzif.transitions.civil_ends.shrink_to_fit();
107
0
        tzif.transitions.infos.shrink_to_fit();
108
109
0
        Ok(tzif)
110
0
    }
111
112
0
    fn parse32<'b>(
113
0
        name: Option<String>,
114
0
        header32: Header,
115
0
        bytes: &'b [u8],
116
0
    ) -> Result<(TzifOwned, &'b [u8]), TzifError> {
117
0
        let mut tzif = TzifOwned {
118
0
            fixed: TzifFixed {
119
0
                name,
120
0
                version: header32.version,
121
0
                // filled in later
122
0
                checksum: 0,
123
0
                designations: String::new(),
124
0
                posix_tz: None,
125
0
            },
126
0
            types: vec![],
127
0
            transitions: TzifTransitions {
128
0
                timestamps: vec![],
129
0
                civil_starts: vec![],
130
0
                civil_ends: vec![],
131
0
                infos: vec![],
132
0
            },
133
0
        };
134
0
        let rest = tzif.parse_transitions(&header32, bytes)?;
135
0
        let rest = tzif.parse_transition_types(&header32, rest)?;
136
0
        let rest = tzif.parse_local_time_types(&header32, rest)?;
137
0
        let rest = tzif.parse_time_zone_designations(&header32, rest)?;
138
0
        let rest = tzif.parse_leap_seconds(&header32, rest)?;
139
0
        let rest = tzif.parse_indicators(&header32, rest)?;
140
0
        Ok((tzif, rest))
141
0
    }
142
143
0
    fn parse64<'b>(
144
0
        name: Option<String>,
145
0
        header32: Header,
146
0
        bytes: &'b [u8],
147
0
    ) -> Result<(TzifOwned, &'b [u8]), TzifError> {
148
0
        let (_, rest) =
149
0
            try_split_at(SplitAtError::V1, bytes, header32.data_block_len()?)?;
150
0
        let (header64, rest) =
151
0
            Header::parse(8, rest).map_err(TzifErrorKind::Header64)?;
152
0
        let mut tzif = TzifOwned {
153
0
            fixed: TzifFixed {
154
0
                name,
155
0
                version: header64.version,
156
0
                // filled in later
157
0
                checksum: 0,
158
0
                designations: String::new(),
159
0
                posix_tz: None,
160
0
            },
161
0
            types: vec![],
162
0
            transitions: TzifTransitions {
163
0
                timestamps: vec![],
164
0
                civil_starts: vec![],
165
0
                civil_ends: vec![],
166
0
                infos: vec![],
167
0
            },
168
0
        };
169
0
        let rest = tzif.parse_transitions(&header64, rest)?;
170
0
        let rest = tzif.parse_transition_types(&header64, rest)?;
171
0
        let rest = tzif.parse_local_time_types(&header64, rest)?;
172
0
        let rest = tzif.parse_time_zone_designations(&header64, rest)?;
173
0
        let rest = tzif.parse_leap_seconds(&header64, rest)?;
174
0
        let rest = tzif.parse_indicators(&header64, rest)?;
175
0
        let rest = tzif.parse_footer(&header64, rest)?;
176
        // Note that we don't check that the TZif data is fully valid. It is
177
        // possible for it to contain superfluous information. For example, a
178
        // non-zero local time type that is never referenced by a transition.
179
0
        Ok((tzif, rest))
180
0
    }
181
182
0
    fn parse_transitions<'b>(
183
0
        &mut self,
184
0
        header: &Header,
185
0
        bytes: &'b [u8],
186
0
    ) -> Result<&'b [u8], TzifError> {
187
0
        let (bytes, rest) = try_split_at(
188
0
            SplitAtError::TransitionTimes,
189
0
            bytes,
190
0
            header.transition_times_len()?,
191
0
        )?;
192
0
        let mut it = bytes.chunks_exact(header.time_size);
193
        // RFC 8536 says: "If there are no transitions, local time for all
194
        // timestamps is specified by the TZ string in the footer if present
195
        // and nonempty; otherwise, it is specified by time type 0."
196
        //
197
        // RFC 8536 also says: "Local time for timestamps before the first
198
        // transition is specified by the first time type (time type
199
        // 0)."
200
        //
201
        // So if there are no transitions, pushing this dummy one will result
202
        // in the desired behavior even when it's the only transition.
203
        // Similarly, since this is the minimum timestamp value, it will
204
        // trigger for any times before the first transition found in the TZif
205
        // data.
206
0
        self.transitions.add_with_type_index(TIMESTAMP_MIN, 0);
207
0
        while let Some(chunk) = it.next() {
208
0
            let mut timestamp = if header.is_32bit() {
209
0
                i64::from(from_be_bytes_i32(chunk))
210
            } else {
211
0
                from_be_bytes_i64(chunk)
212
            };
213
0
            if !(TIMESTAMP_MIN <= timestamp && timestamp <= TIMESTAMP_MAX) {
214
0
                // We really shouldn't error here just because the Unix
215
0
                // timestamp is outside what Jiff supports. Since what Jiff
216
0
                // supports is _somewhat_ arbitrary. But Jiff's supported
217
0
                // range is good enough for all realistic purposes, so we
218
0
                // just clamp an out-of-range Unix timestamp to the Jiff
219
0
                // min or max value.
220
0
                //
221
0
                // This can't result in the sorting order being wrong, but
222
0
                // it can result in a transition that is duplicative with
223
0
                // the dummy transition we inserted above. This should be
224
0
                // fine.
225
0
                let clamped = timestamp.clamp(TIMESTAMP_MIN, TIMESTAMP_MAX);
226
0
                // only-jiff-start
227
0
                warn!(
228
0
                    "found Unix timestamp `{timestamp}` that is outside \
229
0
                     Jiff's supported range, clamping to `{clamped}`",
230
0
                );
231
0
                // only-jiff-end
232
0
                timestamp = clamped;
233
0
            }
234
0
            self.transitions.add(timestamp);
235
        }
236
0
        assert!(it.remainder().is_empty());
237
0
        Ok(rest)
238
0
    }
239
240
0
    fn parse_transition_types<'b>(
241
0
        &mut self,
242
0
        header: &Header,
243
0
        bytes: &'b [u8],
244
0
    ) -> Result<&'b [u8], TransitionTypeError> {
245
0
        let (bytes, rest) = try_split_at(
246
0
            SplitAtError::TransitionTypes,
247
0
            bytes,
248
0
            header.transition_types_len(),
249
0
        )?;
250
        // We skip the first transition because it is our minimum dummy
251
        // transition.
252
0
        for (transition_index, &type_index) in (1..).zip(bytes) {
253
0
            if usize::from(type_index) >= header.tzh_typecnt {
254
0
                return Err(TransitionTypeError::ExceedsLocalTimeTypes);
255
0
            }
256
0
            self.transitions.infos[transition_index].type_index = type_index;
257
        }
258
0
        Ok(rest)
259
0
    }
260
261
0
    fn parse_local_time_types<'b>(
262
0
        &mut self,
263
0
        header: &Header,
264
0
        bytes: &'b [u8],
265
0
    ) -> Result<&'b [u8], TzifError> {
266
0
        let (bytes, rest) = try_split_at(
267
0
            SplitAtError::LocalTimeTypes,
268
0
            bytes,
269
0
            header.local_time_types_len()?,
270
0
        )?;
271
0
        let mut it = bytes.chunks_exact(6);
272
0
        while let Some(chunk) = it.next() {
273
0
            let offset = from_be_bytes_i32(&chunk[..4]);
274
0
            if !(OFFSET_MIN <= offset && offset <= OFFSET_MAX) {
275
0
                return Err(TzifError::from(
276
0
                    LocalTimeTypeError::InvalidOffset { offset },
277
0
                ));
278
0
            }
279
0
            let is_dst = chunk[4] == 1;
280
0
            let designation = (chunk[5], chunk[5]);
281
0
            self.types.push(TzifLocalTimeType {
282
0
                offset,
283
0
                is_dst,
284
0
                designation,
285
0
                indicator: TzifIndicator::LocalWall,
286
0
            });
287
        }
288
0
        assert!(it.remainder().is_empty());
289
0
        Ok(rest)
290
0
    }
291
292
0
    fn parse_time_zone_designations<'b>(
293
0
        &mut self,
294
0
        header: &Header,
295
0
        bytes: &'b [u8],
296
0
    ) -> Result<&'b [u8], TimeZoneDesignatorError> {
297
0
        let (bytes, rest) = try_split_at(
298
0
            SplitAtError::TimeZoneDesignations,
299
0
            bytes,
300
0
            header.time_zone_designations_len(),
301
0
        )?;
302
0
        self.fixed.designations = String::from_utf8(bytes.to_vec())
303
0
            .map_err(|_| TimeZoneDesignatorError::InvalidUtf8)?;
304
        // Holy hell, this is brutal. The boundary conditions are crazy.
305
0
        for typ in self.types.iter_mut() {
306
0
            let start = usize::from(typ.designation.0);
307
0
            let suffix = self
308
0
                .fixed
309
0
                .designations
310
0
                .get(start..)
311
0
                .ok_or(TimeZoneDesignatorError::InvalidStart)?;
312
0
            let len = suffix
313
0
                .find('\x00')
314
0
                .ok_or(TimeZoneDesignatorError::MissingNul)?;
315
0
            let end = start
316
0
                .checked_add(len)
317
0
                .ok_or(TimeZoneDesignatorError::InvalidLength)?;
318
0
            typ.designation.1 = u8::try_from(end)
319
0
                .map_err(|_| TimeZoneDesignatorError::InvalidEnd)?;
320
        }
321
0
        Ok(rest)
322
0
    }
323
324
    /// This parses the leap second corrections in the TZif data.
325
    ///
326
    /// Note that we only parse and verify them. We don't actually use them.
327
    /// Jiff effectively ignores leap seconds.
328
0
    fn parse_leap_seconds<'b>(
329
0
        &mut self,
330
0
        header: &Header,
331
0
        bytes: &'b [u8],
332
0
    ) -> Result<&'b [u8], TzifError> {
333
0
        let (bytes, rest) = try_split_at(
334
0
            SplitAtError::LeapSeconds,
335
0
            bytes,
336
0
            header.leap_second_len()?,
337
0
        )?;
338
0
        let chunk_len = header
339
0
            .time_size
340
0
            .checked_add(4)
341
0
            .expect("time_size plus 4 fits in usize");
342
0
        let mut it = bytes.chunks_exact(chunk_len);
343
0
        while let Some(chunk) = it.next() {
344
0
            let (occur_bytes, _corr_bytes) = chunk.split_at(header.time_size);
345
0
            let occur = if header.is_32bit() {
346
0
                i64::from(from_be_bytes_i32(occur_bytes))
347
            } else {
348
0
                from_be_bytes_i64(occur_bytes)
349
            };
350
0
            if !(TIMESTAMP_MIN <= occur && occur <= TIMESTAMP_MAX) {
351
0
                // only-jiff-start
352
0
                warn!(
353
0
                    "leap second occurrence `{occur}` is \
354
0
                     not in Jiff's supported range"
355
0
                )
356
0
                // only-jiff-end
357
0
            }
358
        }
359
0
        assert!(it.remainder().is_empty());
360
0
        Ok(rest)
361
0
    }
362
363
0
    fn parse_indicators<'b>(
364
0
        &mut self,
365
0
        header: &Header,
366
0
        bytes: &'b [u8],
367
0
    ) -> Result<&'b [u8], IndicatorError> {
368
0
        let (std_wall_bytes, rest) = try_split_at(
369
0
            SplitAtError::StandardWallIndicators,
370
0
            bytes,
371
0
            header.standard_wall_len(),
372
0
        )?;
373
0
        let (ut_local_bytes, rest) = try_split_at(
374
0
            SplitAtError::UTLocalIndicators,
375
0
            rest,
376
0
            header.ut_local_len(),
377
0
        )?;
378
0
        if std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() {
379
            // This is a weird case, but technically possible only if all
380
            // UT/local indicators are 0. If any are 1, then it's an error,
381
            // because it would require the corresponding std/wall indicator
382
            // to be 1 too. Which it can't be, because there aren't any. So
383
            // we just check that they're all zeros.
384
0
            if ut_local_bytes.iter().any(|&byte| byte != 0) {
385
0
                return Err(IndicatorError::UtLocalNonZero);
386
0
            }
387
0
        } else if !std_wall_bytes.is_empty() && ut_local_bytes.is_empty() {
388
0
            for (i, &byte) in std_wall_bytes.iter().enumerate() {
389
                // Indexing is OK because Header guarantees that the number of
390
                // indicators is 0 or equal to the number of types.
391
0
                self.types[i].indicator = if byte == 0 {
392
0
                    TzifIndicator::LocalWall
393
0
                } else if byte == 1 {
394
0
                    TzifIndicator::LocalStandard
395
                } else {
396
0
                    return Err(IndicatorError::InvalidStdWallIndicator);
397
                };
398
            }
399
0
        } else if !std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() {
400
0
            assert_eq!(std_wall_bytes.len(), ut_local_bytes.len());
401
0
            let it = std_wall_bytes.iter().zip(ut_local_bytes);
402
0
            for (i, (&stdwall, &utlocal)) in it.enumerate() {
403
                // Indexing is OK because Header guarantees that the number of
404
                // indicators is 0 or equal to the number of types.
405
0
                self.types[i].indicator = match (stdwall, utlocal) {
406
0
                    (0, 0) => TzifIndicator::LocalWall,
407
0
                    (1, 0) => TzifIndicator::LocalStandard,
408
0
                    (1, 1) => TzifIndicator::UTStandard,
409
                    (0, 1) => {
410
0
                        return Err(IndicatorError::InvalidUtWallCombination);
411
                    }
412
0
                    _ => return Err(IndicatorError::InvalidCombination),
413
                };
414
            }
415
        } else {
416
            // If they're both empty then we don't need to do anything. Every
417
            // local time type record already has the correct default for this
418
            // case set.
419
0
            debug_assert!(std_wall_bytes.is_empty());
420
0
            debug_assert!(ut_local_bytes.is_empty());
421
        }
422
0
        Ok(rest)
423
0
    }
424
425
0
    fn parse_footer<'b>(
426
0
        &mut self,
427
0
        _header: &Header,
428
0
        bytes: &'b [u8],
429
0
    ) -> Result<&'b [u8], FooterError> {
430
0
        if bytes.is_empty() {
431
0
            return Err(FooterError::UnexpectedEnd);
432
0
        }
433
0
        if bytes[0] != b'\n' {
434
0
            return Err(FooterError::MismatchEnd);
435
0
        }
436
0
        let bytes = &bytes[1..];
437
        // Only scan up to 1KB for a NUL terminator in case we somehow got
438
        // passed a huge block of bytes.
439
0
        let toscan = &bytes[..bytes.len().min(1024)];
440
0
        let nlat = toscan
441
0
            .iter()
442
0
            .position(|&b| b == b'\n')
443
0
            .ok_or(FooterError::TerminatorNotFound)?;
444
0
        let (bytes, rest) = bytes.split_at(nlat);
445
0
        if !bytes.is_empty() {
446
            // We could in theory limit TZ strings to their strict POSIX
447
            // definition here for TZif V2, but I don't think there is any
448
            // harm in allowing the extensions in V2 formatted TZif data. Note
449
            // that GNU tooling allows it via the `TZ` environment variable
450
            // even though POSIX doesn't specify it. This all seems okay to me
451
            // because the V3+ extension is a strict superset of functionality.
452
0
            let posix_tz = PosixTimeZone::parse(bytes)
453
0
                .map_err(FooterError::InvalidPosixTz)?;
454
0
            self.fixed.posix_tz = Some(posix_tz);
455
0
        }
456
0
        Ok(&rest[1..])
457
0
    }
458
459
    /// Validates that the POSIX TZ string we parsed (if one exists) is
460
    /// consistent with the last transition in this time zone. This is
461
    /// required by RFC 8536.
462
    ///
463
    /// RFC 8536 says, "If the string is nonempty and one or more
464
    /// transitions appear in the version 2+ data, the string MUST be
465
    /// consistent with the last version 2+ transition."
466
0
    fn verify_posix_time_zone_consistency(
467
0
        &self,
468
0
    ) -> Result<(), InconsistentPosixTimeZoneError> {
469
        // We need to be a little careful, since we always have at least one
470
        // transition (accounting for the dummy `Timestamp::MIN` transition).
471
        // So if we only have 1 transition and a POSIX TZ string, then we
472
        // should not validate it since it's equivalent to the case of 0
473
        // transitions and a POSIX TZ string.
474
0
        if self.transitions.timestamps.len() <= 1 {
475
0
            return Ok(());
476
0
        }
477
0
        let Some(ref tz) = self.fixed.posix_tz else {
478
0
            return Ok(());
479
        };
480
0
        let last = self
481
0
            .transitions
482
0
            .timestamps
483
0
            .last()
484
0
            .expect("last transition timestamp");
485
0
        let type_index = self
486
0
            .transitions
487
0
            .infos
488
0
            .last()
489
0
            .expect("last transition info")
490
0
            .type_index;
491
0
        let typ = &self.types[usize::from(type_index)];
492
0
        let (ioff, abbrev, is_dst) =
493
0
            tz.to_offset_info(ITimestamp::from_second(*last));
494
0
        if ioff.second != typ.offset {
495
0
            return Err(InconsistentPosixTimeZoneError::Offset);
496
0
        }
497
0
        if is_dst != typ.is_dst {
498
0
            return Err(InconsistentPosixTimeZoneError::Dst);
499
0
        }
500
0
        if abbrev != self.designation(&typ) {
501
0
            return Err(InconsistentPosixTimeZoneError::Designation);
502
0
        }
503
0
        Ok(())
504
0
    }
505
506
    /// Add civil datetimes to our transitions.
507
    ///
508
    /// This isn't strictly necessary, but it speeds up time zone lookups when
509
    /// the input is a civil datetime. It lets us do comparisons directly on
510
    /// the civil datetime as given, instead of needing to convert the civil
511
    /// datetime given to a timestamp first. (Even if we didn't do this, I
512
    /// believe we'd still need at least one additional timestamp that is
513
    /// offset, because TZ lookups for a civil datetime are done in local time,
514
    /// and the timestamps in TZif data are, of course, all in UTC.)
515
0
    fn add_civil_datetimes_to_transitions(&mut self) {
516
0
        fn to_datetime(timestamp: i64, offset: i32) -> TzifDateTime {
517
0
            let its = ITimestamp { second: timestamp, nanosecond: 0 };
518
0
            let ioff = IOffset { second: offset };
519
0
            let dt = its.to_datetime(ioff);
520
0
            TzifDateTime::new(
521
0
                dt.date.year,
522
0
                dt.date.month,
523
0
                dt.date.day,
524
0
                dt.time.hour,
525
0
                dt.time.minute,
526
0
                dt.time.second,
527
            )
528
0
        }
529
530
0
        let trans = &mut self.transitions;
531
        // Special case the first transition, which is always a dummy
532
        // transition establishing the lower bound. The loop below doesn't
533
        // handle this correctly because it isn't guaranteed to get a
534
        // `DateTime::MIN` value.
535
0
        trans.infos[0].kind = TzifTransitionKind::Unambiguous;
536
0
        trans.civil_starts[0] = TzifDateTime::MIN;
537
0
        for i in 1..trans.timestamps.len() {
538
0
            let timestamp = trans.timestamps[i];
539
0
            let offset = {
540
0
                let type_index = trans.infos[i].type_index;
541
0
                self.types[usize::from(type_index)].offset
542
            };
543
0
            let prev_offset = {
544
0
                let type_index = trans.infos[i.saturating_sub(1)].type_index;
545
0
                self.types[usize::from(type_index)].offset
546
            };
547
548
0
            if prev_offset == offset {
549
0
                // Equivalent offsets means there can never be any ambiguity.
550
0
                let start = to_datetime(timestamp, prev_offset);
551
0
                trans.infos[i].kind = TzifTransitionKind::Unambiguous;
552
0
                trans.civil_starts[i] = start;
553
0
            } else if prev_offset < offset {
554
0
                // When the offset of the previous transition is less, that
555
0
                // means there is some non-zero amount of time that is
556
0
                // "skipped" when moving to the next transition. Thus, we have
557
0
                // a gap. The start of the gap is the offset which gets us the
558
0
                // earliest time, i.e., the smaller of the two offsets.
559
0
                trans.infos[i].kind = TzifTransitionKind::Gap;
560
0
                trans.civil_starts[i] = to_datetime(timestamp, prev_offset);
561
0
                trans.civil_ends[i] = to_datetime(timestamp, offset);
562
0
            } else {
563
                // When the offset of the previous transition is greater, that
564
                // means there is some non-zero amount of time that will be
565
                // replayed on a wall clock in this time zone. Thus, we have
566
                // a fold. The start of the gold is the offset which gets us
567
                // the earliest time, i.e., the smaller of the two offsets.
568
0
                assert!(prev_offset > offset);
569
0
                trans.infos[i].kind = TzifTransitionKind::Fold;
570
0
                trans.civil_starts[i] = to_datetime(timestamp, offset);
571
0
                trans.civil_ends[i] = to_datetime(timestamp, prev_offset);
572
            }
573
        }
574
0
    }
575
576
    /// Fatten up this TZif data with additional transitions.
577
    ///
578
    /// These additional transitions often make time zone lookups faster, and
579
    /// they smooth out the performance difference between using "slim" and
580
    /// "fat" tzdbs.
581
0
    fn fatten(&mut self) {
582
        // Note that this is a crate feature for *both* `jiff` and
583
        // `jiff-static`.
584
0
        if !cfg!(feature = "tz-fat") {
585
0
            return;
586
0
        }
587
0
        let Some(posix_tz) = self.fixed.posix_tz.clone() else { return };
588
0
        let last =
589
0
            self.transitions.timestamps.last().expect("last transition");
590
0
        let mut i = 0;
591
0
        let mut prev = ITimestamp::from_second(*last);
592
        loop {
593
0
            if i > FATTEN_MAX_TRANSITIONS {
594
                // only-jiff-start
595
                warn!(
596
                    "fattening TZif data for `{name:?}` somehow generated \
597
                     more than {max} transitions, so giving up to avoid \
598
                     doing too much work",
599
                    name = self.fixed.name,
600
                    max = FATTEN_MAX_TRANSITIONS,
601
                );
602
                // only-jiff-end
603
0
                return;
604
0
            }
605
0
            i += 1;
606
0
            prev = match self.add_transition(&posix_tz, prev) {
607
0
                None => break,
608
0
                Some(next) => next,
609
            };
610
        }
611
0
    }
612
613
    /// If there's a transition strictly after the given timestamp for the
614
    /// given POSIX time zone, then add it to this TZif data.
615
0
    fn add_transition(
616
0
        &mut self,
617
0
        posix_tz: &PosixTimeZone<Abbreviation>,
618
0
        prev: ITimestamp,
619
0
    ) -> Option<ITimestamp> {
620
0
        let (its, ioff, abbrev, is_dst) = posix_tz.next_transition(prev)?;
621
0
        if its.to_datetime(IOffset::UTC).date.year >= FATTEN_UP_TO_YEAR {
622
0
            return None;
623
0
        }
624
0
        let type_index =
625
0
            self.find_or_create_local_time_type(ioff, abbrev, is_dst)?;
626
0
        self.transitions.add_with_type_index(its.second, type_index);
627
0
        Some(its)
628
0
    }
629
630
    /// Look for a local time type matching the data given.
631
    ///
632
    /// If one could not be found, then one is created and its index is
633
    /// returned.
634
    ///
635
    /// If one could not be found and one could not be created (e.g., the index
636
    /// would overflow `u8`), then `None` is returned.
637
0
    fn find_or_create_local_time_type(
638
0
        &mut self,
639
0
        offset: IOffset,
640
0
        abbrev: &str,
641
0
        is_dst: bool,
642
0
    ) -> Option<u8> {
643
0
        for (i, typ) in self.types.iter().enumerate() {
644
0
            if offset.second == typ.offset
645
0
                && abbrev == self.designation(typ)
646
0
                && is_dst == typ.is_dst
647
            {
648
0
                return u8::try_from(i).ok();
649
0
            }
650
        }
651
0
        let i = u8::try_from(self.types.len()).ok()?;
652
0
        let designation = self.find_or_create_designation(abbrev)?;
653
0
        self.types.push(TzifLocalTimeType {
654
0
            offset: offset.second,
655
0
            is_dst,
656
0
            designation,
657
0
            // Not really clear if this is correct, but Jiff
658
0
            // ignores this anyway, so ¯\_(ツ)_/¯.
659
0
            indicator: TzifIndicator::LocalWall,
660
0
        });
661
0
        Some(i)
662
0
    }
663
664
    /// Look for a designation (i.e., time zone abbreviation) matching the data
665
    /// given, and return its range into `self.fixed.designations`.
666
    ///
667
    /// If one could not be found, then one is created and its range is
668
    /// returned.
669
    ///
670
    /// If one could not be found and one could not be created (e.g., the range
671
    /// would overflow `u8`), then `None` is returned.
672
0
    fn find_or_create_designation(
673
0
        &mut self,
674
0
        needle: &str,
675
0
    ) -> Option<(u8, u8)> {
676
0
        let mut start = 0;
677
0
        while let Some(offset) = self.fixed.designations[start..].find('\0') {
678
0
            let end = start + offset;
679
0
            let abbrev = &self.fixed.designations[start..end];
680
0
            if needle == abbrev {
681
0
                return Some((start.try_into().ok()?, end.try_into().ok()?));
682
0
            }
683
0
            start = end + 1;
684
        }
685
686
        // Now we need to add a new abbreviation. This
687
        // should generally only happen for malformed TZif
688
        // data. i.e., TZif data with a POSIX time zone that
689
        // contains an TZ abbreviation that isn't found in
690
        // the TZif's designation list.
691
        //
692
        // And since we're guarding against malformed data,
693
        // the designation list might not end with NUL. If
694
        // not, add one.
695
0
        if !self.fixed.designations.ends_with('\0') {
696
0
            self.fixed.designations.push('\0');
697
0
        }
698
0
        let start = self.fixed.designations.len();
699
0
        self.fixed.designations.push_str(needle);
700
0
        self.fixed.designations.push('\0');
701
0
        let end = self.fixed.designations.len();
702
0
        Some((start.try_into().ok()?, end.try_into().ok()?))
703
0
    }
704
705
0
    fn designation(&self, typ: &TzifLocalTimeType) -> &str {
706
0
        let range =
707
0
            usize::from(typ.designation.0)..usize::from(typ.designation.1);
708
        // OK because we verify that the designation range on every local
709
        // time type is a valid range into `self.designations`.
710
0
        &self.fixed.designations[range]
711
0
    }
712
}
713
714
impl TzifTransitionsOwned {
715
    /// Add a single transition with the given timestamp.
716
    ///
717
    /// This also fills in the other columns (civil starts, civil ends and
718
    /// infos) with sensible default values. It is expected that callers will
719
    /// later fill them in.
720
0
    fn add(&mut self, timestamp: i64) {
721
0
        self.add_with_type_index(timestamp, 0);
722
0
    }
723
724
    /// Like `TzifTransitionsOwned::add`, but let's the caller provide a type
725
    /// index if it is known.
726
0
    fn add_with_type_index(&mut self, timestamp: i64, type_index: u8) {
727
0
        self.timestamps.push(timestamp);
728
0
        self.civil_starts.push(TzifDateTime::ZERO);
729
0
        self.civil_ends.push(TzifDateTime::ZERO);
730
0
        self.infos.push(TzifTransitionInfo {
731
0
            type_index,
732
0
            kind: TzifTransitionKind::Unambiguous,
733
0
        });
734
0
    }
735
}
736
737
/// The header for a TZif formatted file.
738
///
739
/// V2+ TZif format have two headers: one for V1 data, and then a second
740
/// following the V1 data block that describes another data block which uses
741
/// 64-bit timestamps. The two headers both have the same format and both
742
/// use 32-bit big-endian encoded integers.
743
#[derive(Debug)]
744
struct Header {
745
    /// The size of the timestamps encoded in the data block.
746
    ///
747
    /// This is guaranteed to be either 4 (for V1) or 8 (for the 64-bit header
748
    /// block in V2+).
749
    time_size: usize,
750
    /// The file format version.
751
    ///
752
    /// Note that this is either a NUL byte (for version 1), or an ASCII byte
753
    /// corresponding to the version number. That is, `0x32` for `2`, `0x33`
754
    /// for `3` or `0x34` for `4`. Note also that just because zoneinfo might
755
    /// have been recently generated does not mean it uses the latest format
756
    /// version. It seems like newer versions are only compiled by `zic` when
757
    /// they are needed. For example, `America/New_York` on my system (as of
758
    /// `2024-03-25`) has version `0x32`, but `Asia/Jerusalem` has version
759
    /// `0x33`.
760
    version: u8,
761
    /// Number of UT/local indicators stored in the file.
762
    ///
763
    /// This is checked to be either equal to `0` or equal to `tzh_typecnt`.
764
    tzh_ttisutcnt: usize,
765
    /// The number of standard/wall indicators stored in the file.
766
    ///
767
    /// This is checked to be either equal to `0` or equal to `tzh_typecnt`.
768
    tzh_ttisstdcnt: usize,
769
    /// The number of leap seconds for which data entries are stored in the
770
    /// file.
771
    tzh_leapcnt: usize,
772
    /// The number of transition times for which data entries are stored in
773
    /// the file.
774
    tzh_timecnt: usize,
775
    /// The number of local time types for which data entries are stored in the
776
    /// file.
777
    ///
778
    /// This is checked to be at least `1`.
779
    tzh_typecnt: usize,
780
    /// The number of bytes of time zone abbreviation strings stored in the
781
    /// file.
782
    ///
783
    /// This is checked to be at least `1`.
784
    tzh_charcnt: usize,
785
}
786
787
impl Header {
788
    /// Parse the header record from the given bytes.
789
    ///
790
    /// Upon success, return the header and all bytes after the header.
791
    ///
792
    /// The given `time_size` must be 4 or 8, corresponding to either the
793
    /// V1 header block or the V2+ header block, respectively.
794
0
    fn parse(
795
0
        time_size: usize,
796
0
        bytes: &[u8],
797
0
    ) -> Result<(Header, &[u8]), HeaderError> {
798
0
        assert!(time_size == 4 || time_size == 8, "time size must be 4 or 8");
799
0
        if bytes.len() < 44 {
800
0
            return Err(HeaderError::TooShort);
801
0
        }
802
0
        let (magic, rest) = bytes.split_at(4);
803
0
        if magic != b"TZif" {
804
0
            return Err(HeaderError::MismatchMagic);
805
0
        }
806
0
        let (version, rest) = rest.split_at(1);
807
0
        let (_reserved, rest) = rest.split_at(15);
808
809
0
        let (tzh_ttisutcnt_bytes, rest) = rest.split_at(4);
810
0
        let (tzh_ttisstdcnt_bytes, rest) = rest.split_at(4);
811
0
        let (tzh_leapcnt_bytes, rest) = rest.split_at(4);
812
0
        let (tzh_timecnt_bytes, rest) = rest.split_at(4);
813
0
        let (tzh_typecnt_bytes, rest) = rest.split_at(4);
814
0
        let (tzh_charcnt_bytes, rest) = rest.split_at(4);
815
816
0
        let tzh_ttisutcnt =
817
0
            from_be_bytes_u32_to_usize(tzh_ttisutcnt_bytes).map_err(|e| {
818
0
                HeaderError::ParseCount { kind: CountKind::Ut, convert: e }
819
0
            })?;
820
0
        let tzh_ttisstdcnt =
821
0
            from_be_bytes_u32_to_usize(tzh_ttisstdcnt_bytes).map_err(|e| {
822
0
                HeaderError::ParseCount { kind: CountKind::Std, convert: e }
823
0
            })?;
824
0
        let tzh_leapcnt =
825
0
            from_be_bytes_u32_to_usize(tzh_leapcnt_bytes).map_err(|e| {
826
0
                HeaderError::ParseCount { kind: CountKind::Leap, convert: e }
827
0
            })?;
828
0
        let tzh_timecnt =
829
0
            from_be_bytes_u32_to_usize(tzh_timecnt_bytes).map_err(|e| {
830
0
                HeaderError::ParseCount { kind: CountKind::Time, convert: e }
831
0
            })?;
832
0
        let tzh_typecnt =
833
0
            from_be_bytes_u32_to_usize(tzh_typecnt_bytes).map_err(|e| {
834
0
                HeaderError::ParseCount { kind: CountKind::Type, convert: e }
835
0
            })?;
836
0
        let tzh_charcnt =
837
0
            from_be_bytes_u32_to_usize(tzh_charcnt_bytes).map_err(|e| {
838
0
                HeaderError::ParseCount { kind: CountKind::Char, convert: e }
839
0
            })?;
840
841
0
        if tzh_ttisutcnt != 0 && tzh_ttisutcnt != tzh_typecnt {
842
0
            return Err(HeaderError::MismatchUtType);
843
0
        }
844
0
        if tzh_ttisstdcnt != 0 && tzh_ttisstdcnt != tzh_typecnt {
845
0
            return Err(HeaderError::MismatchStdType);
846
0
        }
847
0
        if tzh_typecnt < 1 {
848
0
            return Err(HeaderError::ZeroType);
849
0
        }
850
0
        if tzh_charcnt < 1 {
851
0
            return Err(HeaderError::ZeroChar);
852
0
        }
853
854
0
        let header = Header {
855
0
            time_size,
856
0
            version: version[0],
857
0
            tzh_ttisutcnt,
858
0
            tzh_ttisstdcnt,
859
0
            tzh_leapcnt,
860
0
            tzh_timecnt,
861
0
            tzh_typecnt,
862
0
            tzh_charcnt,
863
0
        };
864
0
        Ok((header, rest))
865
0
    }
866
867
    /// Returns true if this header is for a 32-bit data block.
868
    ///
869
    /// When false, it is guaranteed that this header is for a 64-bit data
870
    /// block.
871
0
    fn is_32bit(&self) -> bool {
872
0
        self.time_size == 4
873
0
    }
874
875
    /// Returns the size of the data block, in bytes, for this header.
876
    ///
877
    /// This returns an error if the arithmetic required to compute the
878
    /// length would overflow.
879
    ///
880
    /// This is useful for, e.g., skipping over the 32-bit V1 data block in
881
    /// V2+ TZif formatted files.
882
0
    fn data_block_len(&self) -> Result<usize, HeaderError> {
883
0
        let a = self.transition_times_len()?;
884
0
        let b = self.transition_types_len();
885
0
        let c = self.local_time_types_len()?;
886
0
        let d = self.time_zone_designations_len();
887
0
        let e = self.leap_second_len()?;
888
0
        let f = self.standard_wall_len();
889
0
        let g = self.ut_local_len();
890
0
        a.checked_add(b)
891
0
            .and_then(|z| z.checked_add(c))
892
0
            .and_then(|z| z.checked_add(d))
893
0
            .and_then(|z| z.checked_add(e))
894
0
            .and_then(|z| z.checked_add(f))
895
0
            .and_then(|z| z.checked_add(g))
896
0
            .ok_or(HeaderError::InvalidDataBlock { version: self.version })
897
0
    }
898
899
0
    fn transition_times_len(&self) -> Result<usize, HeaderError> {
900
0
        self.tzh_timecnt
901
0
            .checked_mul(self.time_size)
902
0
            .ok_or(HeaderError::InvalidTimeCount)
903
0
    }
904
905
0
    fn transition_types_len(&self) -> usize {
906
0
        self.tzh_timecnt
907
0
    }
908
909
0
    fn local_time_types_len(&self) -> Result<usize, HeaderError> {
910
0
        self.tzh_typecnt.checked_mul(6).ok_or(HeaderError::InvalidTypeCount)
911
0
    }
912
913
0
    fn time_zone_designations_len(&self) -> usize {
914
0
        self.tzh_charcnt
915
0
    }
916
917
0
    fn leap_second_len(&self) -> Result<usize, HeaderError> {
918
0
        let record_len = self
919
0
            .time_size
920
0
            .checked_add(4)
921
0
            .expect("4-or-8 plus 4 always fits in usize");
922
0
        self.tzh_leapcnt
923
0
            .checked_mul(record_len)
924
0
            .ok_or(HeaderError::InvalidLeapSecondCount)
925
0
    }
926
927
0
    fn standard_wall_len(&self) -> usize {
928
0
        self.tzh_ttisstdcnt
929
0
    }
930
931
0
    fn ut_local_len(&self) -> usize {
932
0
        self.tzh_ttisutcnt
933
0
    }
934
}
935
936
#[derive(Clone, Debug, Eq, PartialEq)]
937
pub(crate) struct TzifError {
938
    kind: TzifErrorKind,
939
}
940
941
#[derive(Clone, Debug, Eq, PartialEq)]
942
enum TzifErrorKind {
943
    Footer(FooterError),
944
    Header(HeaderError),
945
    Header32(HeaderError),
946
    Header64(HeaderError),
947
    InconsistentPosixTimeZone(InconsistentPosixTimeZoneError),
948
    Indicator(IndicatorError),
949
    LocalTimeType(LocalTimeTypeError),
950
    SplitAt(SplitAtError),
951
    TimeZoneDesignator(TimeZoneDesignatorError),
952
    TransitionType(TransitionTypeError),
953
}
954
955
#[cfg(feature = "std")]
956
impl std::error::Error for TzifError {}
957
958
impl core::fmt::Display for TzifError {
959
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
960
        use self::TzifErrorKind::*;
961
0
        match self.kind {
962
0
            Footer(ref err) => {
963
0
                f.write_str("invalid TZif footer: ")?;
964
0
                err.fmt(f)
965
            }
966
0
            Header(ref err) => {
967
0
                f.write_str("invalid TZif header: ")?;
968
0
                err.fmt(f)
969
            }
970
0
            Header32(ref err) => {
971
0
                f.write_str("invalid 32-bit TZif header: ")?;
972
0
                err.fmt(f)
973
            }
974
0
            Header64(ref err) => {
975
0
                f.write_str("invalid 64-bit TZif header: ")?;
976
0
                err.fmt(f)
977
            }
978
0
            InconsistentPosixTimeZone(ref err) => {
979
0
                f.write_str(
980
0
                    "found inconsistency with \
981
0
                     POSIX time zone transition rule \
982
0
                     in TZif file footer: ",
983
0
                )?;
984
0
                err.fmt(f)
985
            }
986
0
            Indicator(ref err) => {
987
0
                f.write_str("failed to parse indicators: ")?;
988
0
                err.fmt(f)
989
            }
990
0
            LocalTimeType(ref err) => {
991
0
                f.write_str("failed to parse local time types: ")?;
992
0
                err.fmt(f)
993
            }
994
0
            SplitAt(ref err) => err.fmt(f),
995
0
            TimeZoneDesignator(ref err) => {
996
0
                f.write_str("failed to parse time zone designators: ")?;
997
0
                err.fmt(f)
998
            }
999
0
            TransitionType(ref err) => {
1000
0
                f.write_str("failed to parse time zone transition types: ")?;
1001
0
                err.fmt(f)
1002
            }
1003
        }
1004
0
    }
1005
}
1006
1007
impl From<TzifErrorKind> for TzifError {
1008
0
    fn from(kind: TzifErrorKind) -> TzifError {
1009
0
        TzifError { kind }
1010
0
    }
1011
}
1012
1013
#[derive(Clone, Debug, Eq, PartialEq)]
1014
enum TransitionTypeError {
1015
    ExceedsLocalTimeTypes,
1016
    Split(SplitAtError),
1017
}
1018
1019
impl From<TransitionTypeError> for TzifError {
1020
0
    fn from(err: TransitionTypeError) -> TzifError {
1021
0
        TzifErrorKind::TransitionType(err).into()
1022
0
    }
1023
}
1024
1025
impl From<SplitAtError> for TransitionTypeError {
1026
0
    fn from(err: SplitAtError) -> TransitionTypeError {
1027
0
        TransitionTypeError::Split(err)
1028
0
    }
1029
}
1030
1031
impl core::fmt::Display for TransitionTypeError {
1032
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1033
        use self::TransitionTypeError::*;
1034
1035
0
        match *self {
1036
0
            ExceedsLocalTimeTypes => f.write_str(
1037
0
                "found time zone transition type index \
1038
0
                 that exceeds the number of local time types",
1039
            ),
1040
0
            Split(ref err) => err.fmt(f),
1041
        }
1042
0
    }
1043
}
1044
1045
#[derive(Clone, Debug, Eq, PartialEq)]
1046
enum LocalTimeTypeError {
1047
    InvalidOffset { offset: i32 },
1048
    Split(SplitAtError),
1049
}
1050
1051
impl From<LocalTimeTypeError> for TzifError {
1052
0
    fn from(err: LocalTimeTypeError) -> TzifError {
1053
0
        TzifErrorKind::LocalTimeType(err).into()
1054
0
    }
1055
}
1056
1057
impl From<SplitAtError> for LocalTimeTypeError {
1058
0
    fn from(err: SplitAtError) -> LocalTimeTypeError {
1059
0
        LocalTimeTypeError::Split(err)
1060
0
    }
1061
}
1062
1063
impl core::fmt::Display for LocalTimeTypeError {
1064
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1065
        use self::LocalTimeTypeError::*;
1066
1067
0
        match *self {
1068
0
            InvalidOffset { offset } => write!(
1069
0
                f,
1070
                "found local time type with \
1071
                 out-of-bounds time zone offset: {offset}, \
1072
                 Jiff's allowed range is `{OFFSET_MIN}..={OFFSET_MAX}`"
1073
            ),
1074
0
            Split(ref err) => err.fmt(f),
1075
        }
1076
0
    }
1077
}
1078
1079
#[derive(Clone, Debug, Eq, PartialEq)]
1080
enum TimeZoneDesignatorError {
1081
    InvalidEnd,
1082
    InvalidLength,
1083
    InvalidStart,
1084
    InvalidUtf8,
1085
    MissingNul,
1086
    Split(SplitAtError),
1087
}
1088
1089
impl From<TimeZoneDesignatorError> for TzifError {
1090
0
    fn from(err: TimeZoneDesignatorError) -> TzifError {
1091
0
        TzifErrorKind::TimeZoneDesignator(err).into()
1092
0
    }
1093
}
1094
1095
impl From<SplitAtError> for TimeZoneDesignatorError {
1096
0
    fn from(err: SplitAtError) -> TimeZoneDesignatorError {
1097
0
        TimeZoneDesignatorError::Split(err)
1098
0
    }
1099
}
1100
1101
impl core::fmt::Display for TimeZoneDesignatorError {
1102
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1103
        use self::TimeZoneDesignatorError::*;
1104
1105
0
        match *self {
1106
0
            InvalidEnd => f.write_str(
1107
0
                "found invalid end of time zone designator \
1108
0
                 for local time type",
1109
            ),
1110
0
            InvalidLength => f.write_str(
1111
0
                "found invalid length of time zone designator \
1112
0
                 for local time type",
1113
            ),
1114
0
            InvalidStart => f.write_str(
1115
0
                "found invalid start of time zone designator \
1116
0
                 for local time type",
1117
            ),
1118
            InvalidUtf8 => {
1119
0
                f.write_str("found invalid UTF-8 in time zone designators")
1120
            }
1121
0
            MissingNul => f.write_str(
1122
0
                "could not find NUL terminator for time zone designator",
1123
            ),
1124
0
            Split(ref err) => err.fmt(f),
1125
        }
1126
0
    }
1127
}
1128
1129
#[derive(Clone, Debug, Eq, PartialEq)]
1130
enum IndicatorError {
1131
    InvalidCombination,
1132
    InvalidStdWallIndicator,
1133
    InvalidUtWallCombination,
1134
    Split(SplitAtError),
1135
    UtLocalNonZero,
1136
}
1137
1138
impl From<IndicatorError> for TzifError {
1139
0
    fn from(err: IndicatorError) -> TzifError {
1140
0
        TzifErrorKind::Indicator(err).into()
1141
0
    }
1142
}
1143
1144
impl From<SplitAtError> for IndicatorError {
1145
0
    fn from(err: SplitAtError) -> IndicatorError {
1146
0
        IndicatorError::Split(err)
1147
0
    }
1148
}
1149
1150
impl core::fmt::Display for IndicatorError {
1151
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1152
        use self::IndicatorError::*;
1153
1154
0
        match *self {
1155
0
            InvalidCombination => f.write_str(
1156
0
                "found invalid std/wall or UT/local value for \
1157
0
                 local time type, each must be 0 or 1",
1158
            ),
1159
0
            InvalidStdWallIndicator => f.write_str(
1160
0
                "found invalid std/wall indicator, \
1161
0
                 expected it to be 0 or 1",
1162
            ),
1163
0
            InvalidUtWallCombination => f.write_str(
1164
0
                "found invalid UT-wall combination for \
1165
0
                 local time type, only local-wall, \
1166
0
                 local-standard and UT-standard are allowed",
1167
            ),
1168
0
            Split(ref err) => err.fmt(f),
1169
0
            UtLocalNonZero => f.write_str(
1170
0
                "found non-zero UT/local indicator, \
1171
0
                 but all such indicators should be zero",
1172
            ),
1173
        }
1174
0
    }
1175
}
1176
1177
#[derive(Clone, Debug, Eq, PartialEq)]
1178
enum InconsistentPosixTimeZoneError {
1179
    Designation,
1180
    Dst,
1181
    Offset,
1182
}
1183
1184
impl From<InconsistentPosixTimeZoneError> for TzifError {
1185
0
    fn from(err: InconsistentPosixTimeZoneError) -> TzifError {
1186
0
        TzifErrorKind::InconsistentPosixTimeZone(err).into()
1187
0
    }
1188
}
1189
1190
impl core::fmt::Display for InconsistentPosixTimeZoneError {
1191
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1192
        use self::InconsistentPosixTimeZoneError::*;
1193
1194
0
        match *self {
1195
0
            Designation => f.write_str(
1196
0
                "expected last transition in TZif file to have \
1197
0
                 a time zone abbreviation matching the abbreviation \
1198
0
                 derived from the POSIX time zone transition rule",
1199
            ),
1200
0
            Dst => f.write_str(
1201
0
                "expected last transition in TZif file to have \
1202
0
                 a DST status matching the status derived from the \
1203
0
                 POSIX time zone transition rule",
1204
            ),
1205
0
            Offset => f.write_str(
1206
0
                "expected last transition in TZif file to have \
1207
0
                 DST offset matching the offset derived from the \
1208
0
                 POSIX time zone transition rule",
1209
            ),
1210
        }
1211
0
    }
1212
}
1213
1214
#[derive(Clone, Debug, Eq, PartialEq)]
1215
enum FooterError {
1216
    InvalidPosixTz(crate::shared::posix::PosixTimeZoneError),
1217
    MismatchEnd,
1218
    TerminatorNotFound,
1219
    UnexpectedEnd,
1220
}
1221
1222
impl From<FooterError> for TzifError {
1223
0
    fn from(err: FooterError) -> TzifError {
1224
0
        TzifErrorKind::Footer(err).into()
1225
0
    }
1226
}
1227
1228
impl core::fmt::Display for FooterError {
1229
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1230
        use self::FooterError::*;
1231
1232
0
        match *self {
1233
0
            InvalidPosixTz(ref err) => {
1234
0
                f.write_str("invalid POSIX time zone transition rule")?;
1235
0
                core::fmt::Display::fmt(err, f)
1236
            }
1237
0
            MismatchEnd => f.write_str(
1238
0
                "expected to find `\\n` at the beginning of \
1239
0
                 the TZif file footer, \
1240
0
                 but found something else instead",
1241
            ),
1242
0
            TerminatorNotFound => f.write_str(
1243
0
                "expected to find `\\n` terminating \
1244
0
                 the TZif file footer, \
1245
0
                 but no line terminator could be found",
1246
            ),
1247
0
            UnexpectedEnd => f.write_str(
1248
0
                "expected to find `\\n` at the beginning of \
1249
0
                 the TZif file footer, \
1250
0
                 but found unexpected end of data",
1251
            ),
1252
        }
1253
0
    }
1254
}
1255
1256
#[derive(Clone, Debug, Eq, PartialEq)]
1257
enum HeaderError {
1258
    InvalidDataBlock { version: u8 },
1259
    InvalidLeapSecondCount,
1260
    InvalidTimeCount,
1261
    InvalidTypeCount,
1262
    MismatchMagic,
1263
    MismatchStdType,
1264
    MismatchUtType,
1265
    ParseCount { kind: CountKind, convert: U32UsizeError },
1266
    TooShort,
1267
    ZeroChar,
1268
    ZeroType,
1269
}
1270
1271
impl From<HeaderError> for TzifError {
1272
0
    fn from(err: HeaderError) -> TzifError {
1273
0
        TzifErrorKind::Header(err).into()
1274
0
    }
1275
}
1276
1277
impl core::fmt::Display for HeaderError {
1278
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1279
        use self::HeaderError::*;
1280
1281
0
        match *self {
1282
0
            InvalidDataBlock { version } => write!(
1283
0
                f,
1284
                "length of data block in V{version} TZif file is too big",
1285
            ),
1286
            InvalidLeapSecondCount => {
1287
0
                f.write_str("number of leap seconds is too big")
1288
            }
1289
            InvalidTimeCount => {
1290
0
                f.write_str("number of transition times is too big")
1291
            }
1292
            InvalidTypeCount => {
1293
0
                f.write_str("number of local time types is too big")
1294
            }
1295
0
            MismatchMagic => f.write_str("magic bytes mismatch"),
1296
0
            MismatchStdType => f.write_str(
1297
0
                "expected number of standard/wall indicators to be zero \
1298
0
                 or equal to the number of local time types",
1299
            ),
1300
0
            MismatchUtType => f.write_str(
1301
0
                "expected number of UT/local indicators to be zero \
1302
0
                 or equal to the number of local time types",
1303
            ),
1304
0
            ParseCount { ref kind, ref convert } => {
1305
0
                write!(f, "failed to parse `{kind}`: {convert}")
1306
            }
1307
0
            TooShort => f.write_str("too short"),
1308
0
            ZeroChar => f.write_str(
1309
0
                "expected number of time zone abbreviations fo be at least 1",
1310
            ),
1311
0
            ZeroType => f.write_str(
1312
0
                "expected number of local time types fo be at least 1",
1313
            ),
1314
        }
1315
0
    }
1316
}
1317
1318
#[derive(Clone, Debug, Eq, PartialEq)]
1319
enum CountKind {
1320
    Ut,
1321
    Std,
1322
    Leap,
1323
    Time,
1324
    Type,
1325
    Char,
1326
}
1327
1328
impl core::fmt::Display for CountKind {
1329
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1330
        use self::CountKind::*;
1331
0
        match *self {
1332
0
            Ut => f.write_str("tzh_ttisutcnt"),
1333
0
            Std => f.write_str("tzh_ttisstdcnt"),
1334
0
            Leap => f.write_str("tzh_leapcnt"),
1335
0
            Time => f.write_str("tzh_timecnt"),
1336
0
            Type => f.write_str("tzh_typecnt"),
1337
0
            Char => f.write_str("tzh_charcnt"),
1338
        }
1339
0
    }
1340
}
1341
1342
#[derive(Clone, Debug, Eq, PartialEq)]
1343
pub(crate) enum SplitAtError {
1344
    V1,
1345
    LeapSeconds,
1346
    LocalTimeTypes,
1347
    StandardWallIndicators,
1348
    TimeZoneDesignations,
1349
    TransitionTimes,
1350
    TransitionTypes,
1351
    UTLocalIndicators,
1352
}
1353
1354
impl From<SplitAtError> for TzifError {
1355
0
    fn from(err: SplitAtError) -> TzifError {
1356
0
        TzifErrorKind::SplitAt(err).into()
1357
0
    }
1358
}
1359
1360
impl core::fmt::Display for SplitAtError {
1361
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1362
        use self::SplitAtError::*;
1363
1364
0
        f.write_str("expected bytes for '")?;
1365
0
        f.write_str(match *self {
1366
0
            V1 => "v1 TZif",
1367
0
            LeapSeconds => "leap seconds",
1368
0
            LocalTimeTypes => "local time types",
1369
0
            StandardWallIndicators => "standard/wall indicators",
1370
0
            TimeZoneDesignations => "time zone designations",
1371
0
            TransitionTimes => "transition times",
1372
0
            TransitionTypes => "transition types",
1373
0
            UTLocalIndicators => "UT/local indicators",
1374
0
        })?;
1375
0
        f.write_str("data block', but did not find enough bytes")?;
1376
0
        Ok(())
1377
0
    }
1378
}
1379
1380
#[derive(Clone, Debug, Eq, PartialEq)]
1381
struct U32UsizeError;
1382
1383
impl core::fmt::Display for U32UsizeError {
1384
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1385
0
        write!(
1386
0
            f,
1387
            "failed to parse integer because it is bigger than `{max}`",
1388
            max = usize::MAX,
1389
        )
1390
0
    }
1391
}
1392
1393
/// Splits the given slice of bytes at the index given.
1394
///
1395
/// If the index is out of range (greater than `bytes.len()`) then an error is
1396
/// returned. The error message will include the `what` string given, which is
1397
/// meant to describe the thing being split.
1398
0
fn try_split_at<'b>(
1399
0
    what: SplitAtError,
1400
0
    bytes: &'b [u8],
1401
0
    at: usize,
1402
0
) -> Result<(&'b [u8], &'b [u8]), SplitAtError> {
1403
0
    if at > bytes.len() {
1404
0
        Err(what)
1405
    } else {
1406
0
        Ok(bytes.split_at(at))
1407
    }
1408
0
}
1409
1410
/// Interprets the given slice as an unsigned 32-bit big endian integer,
1411
/// attempts to convert it to a `usize` and returns it.
1412
///
1413
/// # Panics
1414
///
1415
/// When `bytes.len() != 4`.
1416
///
1417
/// # Errors
1418
///
1419
/// This errors if the `u32` parsed from the given bytes cannot fit in a
1420
/// `usize`.
1421
0
fn from_be_bytes_u32_to_usize(bytes: &[u8]) -> Result<usize, U32UsizeError> {
1422
0
    let n = from_be_bytes_u32(bytes);
1423
0
    usize::try_from(n).map_err(|_| U32UsizeError)
1424
0
}
1425
1426
/// Interprets the given slice as an unsigned 32-bit big endian integer and
1427
/// returns it.
1428
///
1429
/// # Panics
1430
///
1431
/// When `bytes.len() != 4`.
1432
0
fn from_be_bytes_u32(bytes: &[u8]) -> u32 {
1433
0
    u32::from_be_bytes(bytes.try_into().unwrap())
1434
0
}
1435
1436
/// Interprets the given slice as a signed 32-bit big endian integer and
1437
/// returns it.
1438
///
1439
/// # Panics
1440
///
1441
/// When `bytes.len() != 4`.
1442
0
fn from_be_bytes_i32(bytes: &[u8]) -> i32 {
1443
0
    i32::from_be_bytes(bytes.try_into().unwrap())
1444
0
}
1445
1446
/// Interprets the given slice as a signed 64-bit big endian integer and
1447
/// returns it.
1448
///
1449
/// # Panics
1450
///
1451
/// When `bytes.len() != 8`.
1452
0
fn from_be_bytes_i64(bytes: &[u8]) -> i64 {
1453
0
    i64::from_be_bytes(bytes.try_into().unwrap())
1454
0
}