1use alloc::{string::String, vec, vec::Vec};
2
3use crate::{
4 tz::{posix, Abbreviation, Dst, Offset},
5 util::{crc32, MaybeStaticSlice},
6};
7
8use super::{
9 DateTime, Indicator, LocalTimeType, TimeZone, Timestamp, TransitionInfo,
10 TransitionKind, Transitions,
11};
12
13const TIMESTAMP_MIN: i64 = Timestamp::MIN.as_second();
18const TIMESTAMP_MAX: i64 = Timestamp::MAX.as_second();
19
20const OFFSET_MIN: i32 = Offset::MIN.seconds();
23const OFFSET_MAX: i32 = Offset::MAX.seconds();
24
25const FATTEN_UP_TO_YEAR: i16 = 2038;
28
29const FATTEN_MAX_TRANSITIONS: usize = 300;
32
33impl TimeZone {
34 pub fn parse(bytes: &[u8]) -> Result<TimeZone, ParseError> {
47 ParsedTimeZone::parse(bytes)
48 }
49}
50
51struct ParsedTimeZone {
58 fixed: ParsedFixed,
59 types: Vec<LocalTimeType>,
60 transitions: ParsedTransitions,
61}
62
63struct ParsedFixed {
64 version: u8,
65 checksum: u32,
66 designations: Vec<Abbreviation>,
67 posix_tz: Option<posix::TimeZone>,
68}
69
70struct ParsedTransitions {
71 timestamps: Vec<Timestamp>,
72 civil_starts: Vec<DateTime>,
73 civil_ends: Vec<DateTime>,
74 infos: Vec<TransitionInfo>,
75}
76
77impl ParsedTimeZone {
78 fn parse(bytes: &[u8]) -> Result<TimeZone, ParseError> {
79 let original = bytes;
80 let (header32, rest) =
81 Header::parse(4, bytes).map_err(ParseErrorKind::Header32)?;
82 let (mut tzif, rest) = if header32.version == 0 {
83 ParsedTimeZone::parse32(header32, rest)?
84 } else {
85 ParsedTimeZone::parse64(header32, rest)?
86 };
87 tzif.fatten();
88 tzif.add_civil_datetimes_to_transitions();
91 tzif.verify_posix_time_zone_consistency()?;
92
93 let tzif_raw_len = original.len() - rest.len();
97 tzif.fixed.checksum = crc32::sum(&original[..tzif_raw_len]);
98
99 tzif.finish()
100 }
101
102 fn new(version: u8) -> ParsedTimeZone {
103 ParsedTimeZone {
104 fixed: ParsedFixed {
105 version,
106 checksum: 0,
107 designations: vec![],
108 posix_tz: None,
109 },
110 types: vec![],
111 transitions: ParsedTransitions {
112 timestamps: vec![],
113 civil_starts: vec![],
114 civil_ends: vec![],
115 infos: vec![],
116 },
117 }
118 }
119
120 fn finish(self) -> Result<TimeZone, ParseError> {
121 Ok(TimeZone {
122 version: self.fixed.version,
123 checksum: self.fixed.checksum,
124 designations: MaybeStaticSlice::heap(
125 self.fixed.designations.into_boxed_slice(),
126 ),
127 posix_tz: self.fixed.posix_tz,
128 types: MaybeStaticSlice::heap(self.types.into_boxed_slice()),
129 transitions: Transitions {
130 timestamps: MaybeStaticSlice::heap(
131 self.transitions.timestamps.into_boxed_slice(),
132 ),
133 civil_starts: MaybeStaticSlice::heap(
134 self.transitions.civil_starts.into_boxed_slice(),
135 ),
136 civil_ends: MaybeStaticSlice::heap(
137 self.transitions.civil_ends.into_boxed_slice(),
138 ),
139 infos: MaybeStaticSlice::heap(
140 self.transitions.infos.into_boxed_slice(),
141 ),
142 },
143 })
144 }
145
146 fn parse32<'b>(
147 header32: Header,
148 bytes: &'b [u8],
149 ) -> Result<(ParsedTimeZone, &'b [u8]), ParseError> {
150 let mut tzif = ParsedTimeZone::new(header32.version);
151 let rest = tzif.parse_transitions(&header32, bytes)?;
152 let rest = tzif.parse_transition_types(&header32, rest)?;
153 let rest = tzif.parse_local_time_types(&header32, rest)?;
154 let rest = tzif.parse_time_zone_designations(&header32, rest)?;
155 let rest = tzif.parse_leap_seconds(&header32, rest)?;
156 let rest = tzif.parse_indicators(&header32, rest)?;
157 Ok((tzif, rest))
158 }
159
160 fn parse64<'b>(
161 header32: Header,
162 bytes: &'b [u8],
163 ) -> Result<(ParsedTimeZone, &'b [u8]), ParseError> {
164 let (_, rest) =
165 try_split_at(SplitAtError::V1, bytes, header32.data_block_len()?)?;
166 let (header64, rest) =
167 Header::parse(8, rest).map_err(ParseErrorKind::Header64)?;
168 let mut tzif = ParsedTimeZone::new(header64.version);
169 let rest = tzif.parse_transitions(&header64, rest)?;
170 let rest = tzif.parse_transition_types(&header64, rest)?;
171 let rest = tzif.parse_local_time_types(&header64, rest)?;
172 let rest = tzif.parse_time_zone_designations(&header64, rest)?;
173 let rest = tzif.parse_leap_seconds(&header64, rest)?;
174 let rest = tzif.parse_indicators(&header64, rest)?;
175 let rest = tzif.parse_footer(&header64, rest)?;
176 Ok((tzif, rest))
180 }
181
182 fn parse_transitions<'b>(
183 &mut self,
184 header: &Header,
185 bytes: &'b [u8],
186 ) -> Result<&'b [u8], ParseError> {
187 let (bytes, rest) = try_split_at(
188 SplitAtError::TransitionTimes,
189 bytes,
190 header.transition_times_len()?,
191 )?;
192 let mut it = bytes.chunks_exact(header.time_size);
193 self.transitions.add_with_type_index(Timestamp::MIN, 0);
206 for chunk in &mut it {
207 let unix_timestamp = if header.is_32bit() {
208 i64::from(from_be_bytes_i32(chunk))
209 } else {
210 from_be_bytes_i64(chunk)
211 };
212 let timestamp = match Timestamp::from_second(unix_timestamp) {
213 Ok(timestamp) => timestamp,
214 Err(_) => {
215 let clamped =
227 if unix_timestamp < Timestamp::MIN.as_second() {
228 Timestamp::MIN
229 } else {
230 Timestamp::MAX
231 };
232 warn!(
233 "found Unix timestamp `{unix_timestamp}` that is \
234 outside Jiff's supported range, \
235 clamping to `{clamped:?}`",
236 );
237 clamped
238 }
239 };
240 self.transitions.add(timestamp);
241 }
242 assert!(it.remainder().is_empty());
243 Ok(rest)
244 }
245
246 fn parse_transition_types<'b>(
247 &mut self,
248 header: &Header,
249 bytes: &'b [u8],
250 ) -> Result<&'b [u8], TransitionTypeError> {
251 let (bytes, rest) = try_split_at(
252 SplitAtError::TransitionTypes,
253 bytes,
254 header.transition_types_len(),
255 )?;
256 for (transition_index, &type_index) in (1..).zip(bytes) {
259 if usize::from(type_index) >= header.tzh_typecnt {
260 return Err(TransitionTypeError::ExceedsLocalTimeTypes);
261 }
262 self.transitions.infos[transition_index].type_index = type_index;
263 }
264 Ok(rest)
265 }
266
267 fn parse_local_time_types<'b>(
268 &mut self,
269 header: &Header,
270 bytes: &'b [u8],
271 ) -> Result<&'b [u8], ParseError> {
272 let (bytes, rest) = try_split_at(
273 SplitAtError::LocalTimeTypes,
274 bytes,
275 header.local_time_types_len()?,
276 )?;
277 let mut it = bytes.chunks_exact(6);
278 for chunk in &mut it {
279 let offset_seconds = from_be_bytes_i32(&chunk[..4]);
280 let offset =
281 Offset::from_seconds(offset_seconds).map_err(|_| {
282 LocalTimeTypeError::InvalidOffset {
283 offset: offset_seconds,
284 }
285 })?;
286 let dst = Dst::from(chunk[4] == 1);
287 let designation = chunk[5];
288 self.types.push(LocalTimeType {
289 offset,
290 dst,
291 designation,
292 indicator: Indicator::LocalWall,
293 });
294 }
295 assert!(it.remainder().is_empty());
296 Ok(rest)
297 }
298
299 fn parse_time_zone_designations<'b>(
300 &mut self,
301 header: &Header,
302 bytes: &'b [u8],
303 ) -> Result<&'b [u8], TimeZoneDesignatorError> {
304 let (bytes, rest) = try_split_at(
305 SplitAtError::TimeZoneDesignations,
306 bytes,
307 header.time_zone_designations_len(),
308 )?;
309 let designations = String::from_utf8(bytes.to_vec())
310 .map_err(|_| TimeZoneDesignatorError::InvalidUtf8)?;
311 for type_index in 0..self.types.len() {
312 let start = usize::from(self.types[type_index].designation);
313 let suffix = designations
314 .get(start..)
315 .ok_or(TimeZoneDesignatorError::InvalidStart)?;
316 let len = suffix
317 .find('\x00')
318 .ok_or(TimeZoneDesignatorError::MissingNul)?;
319 let end = start
320 .checked_add(len)
321 .ok_or(TimeZoneDesignatorError::InvalidLength)?;
322 u8::try_from(end)
323 .map_err(|_| TimeZoneDesignatorError::InvalidEnd)?;
324 let abbreviation = &designations[start..end];
325 let designation = self
329 .find_or_create_designation(abbreviation)
330 .expect("there are at most 256 local time types");
331 self.types[type_index].designation = designation;
332 }
333 Ok(rest)
334 }
335
336 fn parse_leap_seconds<'b>(
341 &mut self,
342 header: &Header,
343 bytes: &'b [u8],
344 ) -> Result<&'b [u8], ParseError> {
345 let (bytes, rest) = try_split_at(
346 SplitAtError::LeapSeconds,
347 bytes,
348 header.leap_second_len()?,
349 )?;
350 let chunk_len = header.time_size.wrapping_add(4);
352 let mut it = bytes.chunks_exact(chunk_len);
353 for chunk in &mut it {
354 let (occur_bytes, _corr_bytes) = chunk.split_at(header.time_size);
355 let occur = if header.is_32bit() {
356 i64::from(from_be_bytes_i32(occur_bytes))
357 } else {
358 from_be_bytes_i64(occur_bytes)
359 };
360 if !(TIMESTAMP_MIN <= occur && occur <= TIMESTAMP_MAX) {
361 warn!(
362 "leap second occurrence `{occur}` is \
363 not in Jiff's supported range"
364 )
365 }
366 }
367 assert!(it.remainder().is_empty());
368 Ok(rest)
369 }
370
371 fn parse_indicators<'b>(
372 &mut self,
373 header: &Header,
374 bytes: &'b [u8],
375 ) -> Result<&'b [u8], IndicatorError> {
376 let (std_wall_bytes, rest) = try_split_at(
377 SplitAtError::StandardWallIndicators,
378 bytes,
379 header.standard_wall_len(),
380 )?;
381 let (ut_local_bytes, rest) = try_split_at(
382 SplitAtError::UTLocalIndicators,
383 rest,
384 header.ut_local_len(),
385 )?;
386 if std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() {
387 if ut_local_bytes.iter().any(|&byte| byte != 0) {
388 return Err(IndicatorError::UtLocalNonZero);
389 }
390 } else if !std_wall_bytes.is_empty() && ut_local_bytes.is_empty() {
391 for (i, &byte) in std_wall_bytes.iter().enumerate() {
392 self.types[i].indicator = if byte == 0 {
393 Indicator::LocalWall
394 } else if byte == 1 {
395 Indicator::LocalStandard
396 } else {
397 return Err(IndicatorError::InvalidStdWallIndicator);
398 };
399 }
400 } else if !std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() {
401 assert_eq!(std_wall_bytes.len(), ut_local_bytes.len());
402 let it = std_wall_bytes.iter().zip(ut_local_bytes);
403 for (i, (&stdwall, &utlocal)) in it.enumerate() {
404 self.types[i].indicator = match (stdwall, utlocal) {
405 (0, 0) => Indicator::LocalWall,
406 (1, 0) => Indicator::LocalStandard,
407 (1, 1) => Indicator::UTStandard,
408 (0, 1) => {
409 return Err(IndicatorError::InvalidUtWallCombination);
410 }
411 _ => return Err(IndicatorError::InvalidCombination),
412 };
413 }
414 } else {
415 debug_assert!(std_wall_bytes.is_empty());
416 debug_assert!(ut_local_bytes.is_empty());
417 }
418 Ok(rest)
419 }
420
421 fn parse_footer<'b>(
422 &mut self,
423 _header: &Header,
424 bytes: &'b [u8],
425 ) -> Result<&'b [u8], FooterError> {
426 if bytes.is_empty() {
427 return Err(FooterError::UnexpectedEnd);
428 }
429 if bytes[0] != b'\n' {
430 return Err(FooterError::MismatchEnd);
431 }
432 let bytes = &bytes[1..];
433 let toscan = &bytes[..bytes.len().min(1024)];
436 let nlat = toscan
437 .iter()
438 .position(|&b| b == b'\n')
439 .ok_or(FooterError::TerminatorNotFound)?;
440 let (bytes, rest) = bytes.split_at(nlat);
441 if !bytes.is_empty() {
442 let posix_tz = posix::TimeZone::parse(bytes)
449 .map_err(FooterError::InvalidPosixTz)?;
450 self.fixed.posix_tz = Some(posix_tz);
451 }
452 Ok(&rest[1..])
453 }
454
455 fn verify_posix_time_zone_consistency(
458 &self,
459 ) -> Result<(), InconsistentPosixTimeZoneError> {
460 if self.transitions.timestamps.len() <= 1 {
461 return Ok(());
462 }
463 let Some(ref tz) = self.fixed.posix_tz else {
464 return Ok(());
465 };
466 let last = self
467 .transitions
468 .timestamps
469 .last()
470 .expect("last transition timestamp");
471 let type_index = self
472 .transitions
473 .infos
474 .last()
475 .expect("last transition info")
476 .type_index;
477 let typ = &self.types[usize::from(type_index)];
478 let timestamp = crate::Timestamp::from_second(last.as_second())
479 .expect("TZif timestamps are in range");
480 let info = tz.to_offset_info(timestamp);
481 if info.offset() != typ.offset {
482 return Err(InconsistentPosixTimeZoneError::Offset);
483 }
484 if info.dst() != typ.dst {
485 return Err(InconsistentPosixTimeZoneError::Dst);
486 }
487 if info.abbreviation().as_ref() != self.designation(typ) {
488 return Err(InconsistentPosixTimeZoneError::Designation);
489 }
490 Ok(())
491 }
492
493 fn add_civil_datetimes_to_transitions(&mut self) {
497 let trans = &mut self.transitions;
498 trans.infos[0].kind = TransitionKind::Unambiguous;
499 trans.civil_starts[0] = DateTime::MIN;
500 trans.civil_ends[0] = DateTime::MIN;
501 for i in 1..trans.timestamps.len() {
502 let timestamp = trans.timestamps[i];
503 let offset = {
504 let type_index = trans.infos[i].type_index;
505 self.types[usize::from(type_index)].offset
506 };
507 let prev_offset = {
508 let type_index = trans.infos[i.saturating_sub(1)].type_index;
509 self.types[usize::from(type_index)].offset
510 };
511
512 if prev_offset == offset {
513 let start = timestamp.to_datetime(prev_offset);
514 trans.infos[i].kind = TransitionKind::Unambiguous;
515 trans.civil_starts[i] = start;
516 trans.civil_ends[i] = start;
517 } else if prev_offset < offset {
518 trans.infos[i].kind = TransitionKind::Gap;
519 trans.civil_starts[i] = timestamp.to_datetime(prev_offset);
520 trans.civil_ends[i] = timestamp.to_datetime(offset);
521 } else {
522 debug_assert!(prev_offset > offset);
523 trans.infos[i].kind = TransitionKind::Fold;
524 trans.civil_starts[i] = timestamp.to_datetime(offset);
525 trans.civil_ends[i] = timestamp.to_datetime(prev_offset);
526 }
527 }
528 }
529
530 fn fatten(&mut self) {
533 if !cfg!(feature = "tz-fat") {
535 return;
536 }
537 let Some(posix_tz) = self.fixed.posix_tz.clone() else {
538 return;
539 };
540 let Some(&last) = self.transitions.timestamps.last() else { return };
541 let mut i = 0;
542 let mut next = last;
543 loop {
544 if i > FATTEN_MAX_TRANSITIONS {
545 warn!(
546 "fattening TZif data for somehow generated more than \
547 {max} transitions, so giving up to avoid \
548 doing too much work",
549 max = FATTEN_MAX_TRANSITIONS,
550 );
551 return;
552 }
553 i += 1;
554 next = match self.fatten_add_transition(&posix_tz, next) {
555 None => break,
556 Some(next) => next,
557 };
558 }
559 }
560
561 fn fatten_add_transition(
562 &mut self,
563 posix_tz: &posix::TimeZone,
564 prev: Timestamp,
565 ) -> Option<Timestamp> {
566 let prev = crate::Timestamp::from_second(prev.as_second()).ok()?;
567 let trans = posix_tz.next_transition(prev)?;
568 if trans.timestamp().to_datetime(Offset::UTC).date().year()
569 >= FATTEN_UP_TO_YEAR
570 {
571 return None;
572 }
573 let type_index = self.find_or_create_local_time_type(
574 trans.offset(),
575 trans.abbreviation().as_ref(),
576 trans.dst(),
577 )?;
578 self.transitions.add_with_type_index(
579 Timestamp::new(trans.timestamp()),
580 type_index,
581 );
582 Some(Timestamp::new(trans.timestamp()))
583 }
584
585 fn find_or_create_local_time_type(
586 &mut self,
587 offset: Offset,
588 abbrev: &str,
589 dst: Dst,
590 ) -> Option<u8> {
591 for (i, typ) in self.types.iter().enumerate() {
592 if offset == typ.offset
593 && abbrev == self.designation(typ)
594 && dst == typ.dst
595 {
596 return u8::try_from(i).ok();
597 }
598 }
599 let i = u8::try_from(self.types.len()).ok()?;
600 let designation = self.find_or_create_designation(abbrev)?;
601 self.types.push(LocalTimeType {
602 offset,
603 dst,
604 designation,
605 indicator: Indicator::LocalWall,
606 });
607 Some(i)
608 }
609
610 fn find_or_create_designation(&mut self, needle: &str) -> Option<u8> {
623 if let Some(i) =
624 self.fixed.designations.iter().position(|abbrev| abbrev == needle)
625 {
626 return u8::try_from(i).ok();
627 }
628 let i = u8::try_from(self.fixed.designations.len()).ok()?;
629 self.fixed.designations.push(Abbreviation::new_or_heap(needle));
630 Some(i)
631 }
632
633 fn designation(&self, typ: &LocalTimeType) -> &str {
634 &self.fixed.designations[usize::from(typ.designation)]
635 }
636}
637
638impl ParsedTransitions {
639 fn add(&mut self, timestamp: Timestamp) {
640 self.add_with_type_index(timestamp, 0);
641 }
642
643 fn add_with_type_index(&mut self, timestamp: Timestamp, type_index: u8) {
644 self.timestamps.push(timestamp);
645 self.civil_starts.push(DateTime::MIN);
646 self.civil_ends.push(DateTime::MIN);
647 self.infos.push(TransitionInfo {
648 type_index,
649 kind: TransitionKind::Unambiguous,
650 });
651 }
652}
653
654#[derive(Debug)]
660struct Header {
661 time_size: usize,
662 version: u8,
663 tzh_ttisutcnt: usize,
664 tzh_ttisstdcnt: usize,
665 tzh_leapcnt: usize,
666 tzh_timecnt: usize,
667 tzh_typecnt: usize,
668 tzh_charcnt: usize,
669}
670
671impl Header {
672 fn parse(
673 time_size: usize,
674 bytes: &[u8],
675 ) -> Result<(Header, &[u8]), HeaderError> {
676 assert!(time_size == 4 || time_size == 8, "time size must be 4 or 8");
677 if bytes.len() < 44 {
678 return Err(HeaderError::TooShort);
679 }
680 let (magic, rest) = bytes.split_at(4);
681 if magic != b"TZif" {
682 return Err(HeaderError::MismatchMagic);
683 }
684 let (version, rest) = rest.split_at(1);
685 let (_reserved, rest) = rest.split_at(15);
686
687 let (tzh_ttisutcnt_bytes, rest) = rest.split_at(4);
688 let (tzh_ttisstdcnt_bytes, rest) = rest.split_at(4);
689 let (tzh_leapcnt_bytes, rest) = rest.split_at(4);
690 let (tzh_timecnt_bytes, rest) = rest.split_at(4);
691 let (tzh_typecnt_bytes, rest) = rest.split_at(4);
692 let (tzh_charcnt_bytes, rest) = rest.split_at(4);
693
694 let tzh_ttisutcnt =
695 from_be_bytes_u32_to_usize(tzh_ttisutcnt_bytes).map_err(|e| {
696 HeaderError::ParseCount { kind: CountKind::Ut, convert: e }
697 })?;
698 let tzh_ttisstdcnt =
699 from_be_bytes_u32_to_usize(tzh_ttisstdcnt_bytes).map_err(|e| {
700 HeaderError::ParseCount { kind: CountKind::Std, convert: e }
701 })?;
702 let tzh_leapcnt =
703 from_be_bytes_u32_to_usize(tzh_leapcnt_bytes).map_err(|e| {
704 HeaderError::ParseCount { kind: CountKind::Leap, convert: e }
705 })?;
706 let tzh_timecnt =
707 from_be_bytes_u32_to_usize(tzh_timecnt_bytes).map_err(|e| {
708 HeaderError::ParseCount { kind: CountKind::Time, convert: e }
709 })?;
710 let tzh_typecnt =
711 from_be_bytes_u32_to_usize(tzh_typecnt_bytes).map_err(|e| {
712 HeaderError::ParseCount { kind: CountKind::Type, convert: e }
713 })?;
714 let tzh_charcnt =
715 from_be_bytes_u32_to_usize(tzh_charcnt_bytes).map_err(|e| {
716 HeaderError::ParseCount { kind: CountKind::Char, convert: e }
717 })?;
718
719 if tzh_ttisutcnt != 0 && tzh_ttisutcnt != tzh_typecnt {
720 return Err(HeaderError::MismatchUtType);
721 }
722 if tzh_ttisstdcnt != 0 && tzh_ttisstdcnt != tzh_typecnt {
723 return Err(HeaderError::MismatchStdType);
724 }
725 if tzh_typecnt < 1 {
726 return Err(HeaderError::ZeroType);
727 }
728 if tzh_charcnt < 1 {
729 return Err(HeaderError::ZeroChar);
730 }
731
732 let header = Header {
733 time_size,
734 version: version[0],
735 tzh_ttisutcnt,
736 tzh_ttisstdcnt,
737 tzh_leapcnt,
738 tzh_timecnt,
739 tzh_typecnt,
740 tzh_charcnt,
741 };
742 Ok((header, rest))
743 }
744
745 fn is_32bit(&self) -> bool {
746 self.time_size == 4
747 }
748
749 fn data_block_len(&self) -> Result<usize, HeaderError> {
750 let a = self.transition_times_len()?;
751 let b = self.transition_types_len();
752 let c = self.local_time_types_len()?;
753 let d = self.time_zone_designations_len();
754 let e = self.leap_second_len()?;
755 let f = self.standard_wall_len();
756 let g = self.ut_local_len();
757 a.checked_add(b)
758 .and_then(|z| z.checked_add(c))
759 .and_then(|z| z.checked_add(d))
760 .and_then(|z| z.checked_add(e))
761 .and_then(|z| z.checked_add(f))
762 .and_then(|z| z.checked_add(g))
763 .ok_or(HeaderError::InvalidDataBlock { version: self.version })
764 }
765
766 fn transition_times_len(&self) -> Result<usize, HeaderError> {
767 self.tzh_timecnt
768 .checked_mul(self.time_size)
769 .ok_or(HeaderError::InvalidTimeCount)
770 }
771
772 fn transition_types_len(&self) -> usize {
773 self.tzh_timecnt
774 }
775
776 fn local_time_types_len(&self) -> Result<usize, HeaderError> {
777 self.tzh_typecnt.checked_mul(6).ok_or(HeaderError::InvalidTypeCount)
778 }
779
780 fn time_zone_designations_len(&self) -> usize {
781 self.tzh_charcnt
782 }
783
784 fn leap_second_len(&self) -> Result<usize, HeaderError> {
785 let record_len = self
786 .time_size
787 .checked_add(4)
788 .expect("4-or-8 plus 4 always fits in usize");
789 self.tzh_leapcnt
790 .checked_mul(record_len)
791 .ok_or(HeaderError::InvalidLeapSecondCount)
792 }
793
794 fn standard_wall_len(&self) -> usize {
795 self.tzh_ttisstdcnt
796 }
797
798 fn ut_local_len(&self) -> usize {
799 self.tzh_ttisutcnt
800 }
801}
802
803#[derive(Clone, Debug, Eq, PartialEq)]
808#[cfg_attr(feature = "defmt", derive(defmt::Format))]
809pub struct ParseError {
810 kind: ParseErrorKind,
811}
812
813#[derive(Clone, Debug, Eq, PartialEq)]
814#[cfg_attr(feature = "defmt", derive(defmt::Format))]
815enum ParseErrorKind {
816 Footer(FooterError),
817 Header(HeaderError),
818 Header32(HeaderError),
819 Header64(HeaderError),
820 InconsistentPosixTimeZone(InconsistentPosixTimeZoneError),
821 Indicator(IndicatorError),
822 LocalTimeType(LocalTimeTypeError),
823 SplitAt(SplitAtError),
824 TimeZoneDesignator(TimeZoneDesignatorError),
825 TransitionType(TransitionTypeError),
826}
827
828#[cfg(feature = "std")]
829impl std::error::Error for ParseError {}
830
831impl core::fmt::Display for ParseError {
832 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
833 use self::ParseErrorKind::*;
834 match self.kind {
835 Footer(ref err) => {
836 f.write_str("invalid TZif footer: ")?;
837 err.fmt(f)
838 }
839 Header(ref err) => {
840 f.write_str("invalid TZif header: ")?;
841 err.fmt(f)
842 }
843 Header32(ref err) => {
844 f.write_str("invalid 32-bit TZif header: ")?;
845 err.fmt(f)
846 }
847 Header64(ref err) => {
848 f.write_str("invalid 64-bit TZif header: ")?;
849 err.fmt(f)
850 }
851 InconsistentPosixTimeZone(ref err) => {
852 f.write_str(
853 "found inconsistency with POSIX time zone transition \
854 rule in TZif file footer: ",
855 )?;
856 err.fmt(f)
857 }
858 Indicator(ref err) => {
859 f.write_str("failed to parse indicators: ")?;
860 err.fmt(f)
861 }
862 LocalTimeType(ref err) => {
863 f.write_str("failed to parse local time types: ")?;
864 err.fmt(f)
865 }
866 SplitAt(ref err) => err.fmt(f),
867 TimeZoneDesignator(ref err) => {
868 f.write_str("failed to parse time zone designators: ")?;
869 err.fmt(f)
870 }
871 TransitionType(ref err) => {
872 f.write_str("failed to parse time zone transition types: ")?;
873 err.fmt(f)
874 }
875 }
876 }
877}
878
879impl From<ParseErrorKind> for ParseError {
880 fn from(kind: ParseErrorKind) -> ParseError {
881 ParseError { kind }
882 }
883}
884
885impl From<HeaderError> for ParseError {
886 fn from(err: HeaderError) -> ParseError {
887 ParseErrorKind::Header(err).into()
888 }
889}
890
891impl From<FooterError> for ParseError {
892 fn from(err: FooterError) -> ParseError {
893 ParseErrorKind::Footer(err).into()
894 }
895}
896
897impl From<InconsistentPosixTimeZoneError> for ParseError {
898 fn from(err: InconsistentPosixTimeZoneError) -> ParseError {
899 ParseErrorKind::InconsistentPosixTimeZone(err).into()
900 }
901}
902
903impl From<IndicatorError> for ParseError {
904 fn from(err: IndicatorError) -> ParseError {
905 ParseErrorKind::Indicator(err).into()
906 }
907}
908
909impl From<LocalTimeTypeError> for ParseError {
910 fn from(err: LocalTimeTypeError) -> ParseError {
911 ParseErrorKind::LocalTimeType(err).into()
912 }
913}
914
915impl From<SplitAtError> for ParseError {
916 fn from(err: SplitAtError) -> ParseError {
917 ParseErrorKind::SplitAt(err).into()
918 }
919}
920
921impl From<TimeZoneDesignatorError> for ParseError {
922 fn from(err: TimeZoneDesignatorError) -> ParseError {
923 ParseErrorKind::TimeZoneDesignator(err).into()
924 }
925}
926
927impl From<TransitionTypeError> for ParseError {
928 fn from(err: TransitionTypeError) -> ParseError {
929 ParseErrorKind::TransitionType(err).into()
930 }
931}
932
933#[derive(Clone, Debug, Eq, PartialEq)]
934#[cfg_attr(feature = "defmt", derive(defmt::Format))]
935enum TransitionTypeError {
936 ExceedsLocalTimeTypes,
937 Split(SplitAtError),
938}
939
940impl From<SplitAtError> for TransitionTypeError {
941 fn from(err: SplitAtError) -> TransitionTypeError {
942 TransitionTypeError::Split(err)
943 }
944}
945
946impl core::fmt::Display for TransitionTypeError {
947 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
948 use self::TransitionTypeError::*;
949 match *self {
950 ExceedsLocalTimeTypes => f.write_str(
951 "found time zone transition type index that exceeds the \
952 number of local time types",
953 ),
954 Split(ref err) => err.fmt(f),
955 }
956 }
957}
958
959#[derive(Clone, Debug, Eq, PartialEq)]
960#[cfg_attr(feature = "defmt", derive(defmt::Format))]
961enum LocalTimeTypeError {
962 InvalidOffset { offset: i32 },
963}
964
965impl core::fmt::Display for LocalTimeTypeError {
966 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
967 use self::LocalTimeTypeError::*;
968 match *self {
969 InvalidOffset { offset } => write!(
970 f,
971 "found local time type with out-of-bounds time zone offset: \
972 {offset}, Jiff's allowed range is \
973 `{OFFSET_MIN}..={OFFSET_MAX}`"
974 ),
975 }
976 }
977}
978
979#[derive(Clone, Debug, Eq, PartialEq)]
980#[cfg_attr(feature = "defmt", derive(defmt::Format))]
981enum TimeZoneDesignatorError {
982 InvalidEnd,
983 InvalidLength,
984 InvalidStart,
985 InvalidUtf8,
986 MissingNul,
987 Split(SplitAtError),
988}
989
990impl From<SplitAtError> for TimeZoneDesignatorError {
991 fn from(err: SplitAtError) -> TimeZoneDesignatorError {
992 TimeZoneDesignatorError::Split(err)
993 }
994}
995
996impl core::fmt::Display for TimeZoneDesignatorError {
997 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
998 use self::TimeZoneDesignatorError::*;
999 match *self {
1000 InvalidEnd => f.write_str(
1001 "found invalid end of time zone designator for local time type",
1002 ),
1003 InvalidLength => f.write_str(
1004 "found invalid length of time zone designator for local time \
1005 type",
1006 ),
1007 InvalidStart => f.write_str(
1008 "found invalid start of time zone designator for local time \
1009 type",
1010 ),
1011 InvalidUtf8 => {
1012 f.write_str("found invalid UTF-8 in time zone designators")
1013 }
1014 MissingNul => f.write_str(
1015 "could not find NUL terminator for time zone designator",
1016 ),
1017 Split(ref err) => err.fmt(f),
1018 }
1019 }
1020}
1021
1022#[derive(Clone, Debug, Eq, PartialEq)]
1023#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1024enum IndicatorError {
1025 InvalidCombination,
1026 InvalidStdWallIndicator,
1027 InvalidUtWallCombination,
1028 Split(SplitAtError),
1029 UtLocalNonZero,
1030}
1031
1032impl From<SplitAtError> for IndicatorError {
1033 fn from(err: SplitAtError) -> IndicatorError {
1034 IndicatorError::Split(err)
1035 }
1036}
1037
1038impl core::fmt::Display for IndicatorError {
1039 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1040 use self::IndicatorError::*;
1041 match *self {
1042 InvalidCombination => f.write_str(
1043 "found invalid std/wall or UT/local value for local time type, \
1044 each must be 0 or 1",
1045 ),
1046 InvalidStdWallIndicator => f.write_str(
1047 "found invalid std/wall indicator, expected it to be 0 or 1",
1048 ),
1049 InvalidUtWallCombination => f.write_str(
1050 "found invalid UT-wall combination for local time type, only \
1051 local-wall, local-standard and UT-standard are allowed",
1052 ),
1053 Split(ref err) => err.fmt(f),
1054 UtLocalNonZero => f.write_str(
1055 "found non-zero UT/local indicator, but all such indicators \
1056 should be zero",
1057 ),
1058 }
1059 }
1060}
1061
1062#[derive(Clone, Debug, Eq, PartialEq)]
1063#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1064enum InconsistentPosixTimeZoneError {
1065 Designation,
1066 Dst,
1067 Offset,
1068}
1069
1070impl core::fmt::Display for InconsistentPosixTimeZoneError {
1071 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1072 use self::InconsistentPosixTimeZoneError::*;
1073 match *self {
1074 Designation => f.write_str(
1075 "expected last transition in TZif file to have a time zone \
1076 abbreviation matching the abbreviation derived from the POSIX \
1077 time zone transition rule",
1078 ),
1079 Dst => f.write_str(
1080 "expected last transition in TZif file to have a DST status \
1081 matching the status derived from the POSIX time zone \
1082 transition rule",
1083 ),
1084 Offset => f.write_str(
1085 "expected last transition in TZif file to have DST offset \
1086 matching the offset derived from the POSIX time zone \
1087 transition rule",
1088 ),
1089 }
1090 }
1091}
1092
1093#[derive(Clone, Debug, Eq, PartialEq)]
1094#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1095enum FooterError {
1096 InvalidPosixTz(posix::ParseError),
1097 MismatchEnd,
1098 TerminatorNotFound,
1099 UnexpectedEnd,
1100}
1101
1102impl core::fmt::Display for FooterError {
1103 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1104 use self::FooterError::*;
1105 match *self {
1106 InvalidPosixTz(ref err) => {
1107 f.write_str("invalid POSIX time zone transition rule")?;
1108 core::fmt::Display::fmt(err, f)
1109 }
1110 MismatchEnd => f.write_str(
1111 "expected to find `\\n` at the beginning of the TZif file \
1112 footer, but found something else instead",
1113 ),
1114 TerminatorNotFound => f.write_str(
1115 "expected to find `\\n` terminating the TZif file footer, but \
1116 no line terminator could be found",
1117 ),
1118 UnexpectedEnd => f.write_str(
1119 "expected to find `\\n` at the beginning of the TZif file \
1120 footer, but found unexpected end of data",
1121 ),
1122 }
1123 }
1124}
1125
1126#[derive(Clone, Debug, Eq, PartialEq)]
1127#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1128enum HeaderError {
1129 InvalidDataBlock { version: u8 },
1130 InvalidLeapSecondCount,
1131 InvalidTimeCount,
1132 InvalidTypeCount,
1133 MismatchMagic,
1134 MismatchStdType,
1135 MismatchUtType,
1136 ParseCount { kind: CountKind, convert: U32UsizeError },
1137 TooShort,
1138 ZeroChar,
1139 ZeroType,
1140}
1141
1142impl core::fmt::Display for HeaderError {
1143 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1144 use self::HeaderError::*;
1145 match *self {
1146 InvalidDataBlock { version } => write!(
1147 f,
1148 "length of data block in V{version} TZif file is too big",
1149 ),
1150 InvalidLeapSecondCount => {
1151 f.write_str("number of leap seconds is too big")
1152 }
1153 InvalidTimeCount => {
1154 f.write_str("number of transition times is too big")
1155 }
1156 InvalidTypeCount => {
1157 f.write_str("number of local time types is too big")
1158 }
1159 MismatchMagic => f.write_str("magic bytes mismatch"),
1160 MismatchStdType => f.write_str(
1161 "expected number of standard/wall indicators to be zero or \
1162 equal to the number of local time types",
1163 ),
1164 MismatchUtType => f.write_str(
1165 "expected number of UT/local indicators to be zero or equal \
1166 to the number of local time types",
1167 ),
1168 ParseCount { ref kind, ref convert } => {
1169 write!(f, "failed to parse `{kind}`: {convert}")
1170 }
1171 TooShort => f.write_str("too short"),
1172 ZeroChar => f.write_str(
1173 "expected number of time zone abbreviations to be at least 1",
1174 ),
1175 ZeroType => f.write_str(
1176 "expected number of local time types to be at least 1",
1177 ),
1178 }
1179 }
1180}
1181
1182#[derive(Clone, Debug, Eq, PartialEq)]
1183#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1184enum CountKind {
1185 Ut,
1186 Std,
1187 Leap,
1188 Time,
1189 Type,
1190 Char,
1191}
1192
1193impl core::fmt::Display for CountKind {
1194 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1195 use self::CountKind::*;
1196 match *self {
1197 Ut => f.write_str("tzh_ttisutcnt"),
1198 Std => f.write_str("tzh_ttisstdcnt"),
1199 Leap => f.write_str("tzh_leapcnt"),
1200 Time => f.write_str("tzh_timecnt"),
1201 Type => f.write_str("tzh_typecnt"),
1202 Char => f.write_str("tzh_charcnt"),
1203 }
1204 }
1205}
1206
1207#[derive(Clone, Debug, Eq, PartialEq)]
1208#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1209enum SplitAtError {
1210 V1,
1211 LeapSeconds,
1212 LocalTimeTypes,
1213 StandardWallIndicators,
1214 TimeZoneDesignations,
1215 TransitionTimes,
1216 TransitionTypes,
1217 UTLocalIndicators,
1218}
1219
1220impl core::fmt::Display for SplitAtError {
1221 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1222 use self::SplitAtError::*;
1223 f.write_str("expected bytes for '")?;
1224 f.write_str(match *self {
1225 V1 => "v1 TZif",
1226 LeapSeconds => "leap seconds",
1227 LocalTimeTypes => "local time types",
1228 StandardWallIndicators => "standard/wall indicators",
1229 TimeZoneDesignations => "time zone designations",
1230 TransitionTimes => "transition times",
1231 TransitionTypes => "transition types",
1232 UTLocalIndicators => "UT/local indicators",
1233 })?;
1234 f.write_str("data block', but did not find enough bytes")?;
1235 Ok(())
1236 }
1237}
1238
1239#[derive(Clone, Debug, Eq, PartialEq)]
1240#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1241struct U32UsizeError;
1242
1243impl core::fmt::Display for U32UsizeError {
1244 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1245 write!(
1246 f,
1247 "failed to parse integer because it is bigger than `{max}`",
1248 max = usize::MAX,
1249 )
1250 }
1251}
1252
1253fn try_split_at<'b>(
1254 what: SplitAtError,
1255 bytes: &'b [u8],
1256 at: usize,
1257) -> Result<(&'b [u8], &'b [u8]), SplitAtError> {
1258 if at > bytes.len() {
1259 Err(what)
1260 } else {
1261 Ok(bytes.split_at(at))
1262 }
1263}
1264
1265fn from_be_bytes_u32_to_usize(bytes: &[u8]) -> Result<usize, U32UsizeError> {
1266 let n = from_be_bytes_u32(bytes);
1267 usize::try_from(n).map_err(|_| U32UsizeError)
1268}
1269
1270fn from_be_bytes_u32(bytes: &[u8]) -> u32 {
1271 u32::from_be_bytes(bytes.try_into().unwrap())
1272}
1273
1274fn from_be_bytes_i32(bytes: &[u8]) -> i32 {
1275 i32::from_be_bytes(bytes.try_into().unwrap())
1276}
1277
1278fn from_be_bytes_i64(bytes: &[u8]) -> i64 {
1279 i64::from_be_bytes(bytes.try_into().unwrap())
1280}
1281
1282#[cfg(test)]
1285mod tests {
1286 use alloc::vec::Vec;
1287
1288 use super::*;
1289
1290 fn push_u32(bytes: &mut Vec<u8>, n: u32) {
1291 bytes.extend_from_slice(&n.to_be_bytes());
1292 }
1293
1294 fn push_header(bytes: &mut Vec<u8>, version: u8) {
1295 bytes.extend_from_slice(b"TZif");
1296 bytes.push(version);
1297 bytes.extend_from_slice(&[0; 15]);
1298 push_u32(bytes, 0); push_u32(bytes, 0); push_u32(bytes, 0); push_u32(bytes, 0); push_u32(bytes, 1); push_u32(bytes, 4); }
1305
1306 fn push_utc_type_and_designation(bytes: &mut Vec<u8>) {
1307 bytes.extend_from_slice(&0i32.to_be_bytes());
1308 bytes.push(0); bytes.push(0); bytes.extend_from_slice(b"UTC\0");
1311 }
1312
1313 fn minimal_v1() -> Vec<u8> {
1314 let mut bytes = vec![];
1315 push_header(&mut bytes, 0);
1316 push_utc_type_and_designation(&mut bytes);
1317 bytes
1318 }
1319
1320 fn minimal_v2(footer: &[u8]) -> Vec<u8> {
1321 let mut bytes = vec![];
1322 push_header(&mut bytes, b'2');
1323 push_utc_type_and_designation(&mut bytes);
1324 push_header(&mut bytes, b'2');
1325 push_utc_type_and_designation(&mut bytes);
1326 bytes.push(b'\n');
1327 bytes.extend_from_slice(footer);
1328 bytes.push(b'\n');
1329 bytes
1330 }
1331
1332 #[test]
1333 fn parse_minimal_v1() {
1334 let tzif = TimeZone::parse(&minimal_v1()).unwrap();
1335 assert_eq!(tzif.version, 0);
1336 assert_eq!(tzif.designations.as_ref(), &[Abbreviation::array("UTC")]);
1337 assert_eq!(tzif.types.len(), 1);
1338 assert_eq!(tzif.types[0].offset, Offset::UTC);
1339 assert_eq!(tzif.transitions.timestamps.len(), 1);
1340 assert_eq!(tzif.transitions.timestamps[0], Timestamp::MIN);
1341 assert_eq!(tzif.transitions.civil_starts[0], DateTime::MIN);
1342 }
1343
1344 #[test]
1345 fn parse_minimal_v2() {
1346 let tzif = TimeZone::parse(&minimal_v2(b"UTC0")).unwrap();
1347 assert_eq!(tzif.version, b'2');
1348 assert!(tzif.posix_tz.is_some());
1349 assert_eq!(tzif.designations.as_ref(), &[Abbreviation::array("UTC")]);
1350 assert_eq!(tzif.types.len(), 1);
1351 assert_eq!(tzif.transitions.timestamps.len(), 1);
1352 }
1353
1354 #[test]
1355 fn parse_minimal_v2_with_and_without_tz_fat() {
1356 let bytes = minimal_v2(b"EST5EDT,M3.2.0,M11.1.0");
1357 let tzif = TimeZone::parse(&bytes).unwrap();
1358 let expected = if cfg!(feature = "tz-fat") { 302 } else { 1 };
1359 assert_eq!(tzif.transitions.timestamps.len(), expected);
1360 }
1361}