1use alloc::{string::String, vec};
2
3use 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
13const TIMESTAMP_MIN: i64 = -377705023201;
23const TIMESTAMP_MAX: i64 = 253402207200;
24
25const OFFSET_MIN: i32 = -93599;
38const OFFSET_MAX: i32 = 93599;
39
40const FATTEN_UP_TO_YEAR: i16 = 2038;
45
46const FATTEN_MAX_TRANSITIONS: usize = 300;
60
61impl TzifOwned {
62 pub(crate) fn parse(
77 name: Option<String>,
78 bytes: &[u8],
79 ) -> Result<TzifOwned, TzifError> {
80 let original = bytes;
81 let name = name.into();
82 let (header32, rest) =
83 Header::parse(4, bytes).map_err(TzifErrorKind::Header32)?;
84 let (mut tzif, rest) = if header32.version == 0 {
85 TzifOwned::parse32(name, header32, rest)?
86 } else {
87 TzifOwned::parse64(name, header32, rest)?
88 };
89 tzif.fatten();
90 tzif.add_civil_datetimes_to_transitions();
93 tzif.verify_posix_time_zone_consistency()?;
94 let tzif_raw_len = (rest.as_ptr() as usize)
96 .checked_sub(original.as_ptr() as usize)
97 .unwrap();
98 let tzif_raw_bytes = &original[..tzif_raw_len];
99 tzif.fixed.checksum = super::crc32::sum(tzif_raw_bytes);
100
101 tzif.fixed.designations.shrink_to_fit();
103 tzif.types.shrink_to_fit();
104 tzif.transitions.timestamps.shrink_to_fit();
105 tzif.transitions.civil_starts.shrink_to_fit();
106 tzif.transitions.civil_ends.shrink_to_fit();
107 tzif.transitions.infos.shrink_to_fit();
108
109 Ok(tzif)
110 }
111
112 fn parse32<'b>(
113 name: Option<String>,
114 header32: Header,
115 bytes: &'b [u8],
116 ) -> Result<(TzifOwned, &'b [u8]), TzifError> {
117 let mut tzif = TzifOwned {
118 fixed: TzifFixed {
119 name,
120 version: header32.version,
121 checksum: 0,
123 designations: String::new(),
124 posix_tz: None,
125 },
126 types: vec![],
127 transitions: TzifTransitions {
128 timestamps: vec![],
129 civil_starts: vec![],
130 civil_ends: vec![],
131 infos: vec![],
132 },
133 };
134 let rest = tzif.parse_transitions(&header32, bytes)?;
135 let rest = tzif.parse_transition_types(&header32, rest)?;
136 let rest = tzif.parse_local_time_types(&header32, rest)?;
137 let rest = tzif.parse_time_zone_designations(&header32, rest)?;
138 let rest = tzif.parse_leap_seconds(&header32, rest)?;
139 let rest = tzif.parse_indicators(&header32, rest)?;
140 Ok((tzif, rest))
141 }
142
143 fn parse64<'b>(
144 name: Option<String>,
145 header32: Header,
146 bytes: &'b [u8],
147 ) -> Result<(TzifOwned, &'b [u8]), TzifError> {
148 let (_, rest) =
149 try_split_at(SplitAtError::V1, bytes, header32.data_block_len()?)?;
150 let (header64, rest) =
151 Header::parse(8, rest).map_err(TzifErrorKind::Header64)?;
152 let mut tzif = TzifOwned {
153 fixed: TzifFixed {
154 name,
155 version: header64.version,
156 checksum: 0,
158 designations: String::new(),
159 posix_tz: None,
160 },
161 types: vec![],
162 transitions: TzifTransitions {
163 timestamps: vec![],
164 civil_starts: vec![],
165 civil_ends: vec![],
166 infos: vec![],
167 },
168 };
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], TzifError> {
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);
207 while let Some(chunk) = it.next() {
208 let mut timestamp = if header.is_32bit() {
209 i64::from(from_be_bytes_i32(chunk))
210 } else {
211 from_be_bytes_i64(chunk)
212 };
213 if !(TIMESTAMP_MIN <= timestamp && timestamp <= TIMESTAMP_MAX) {
214 let clamped = timestamp.clamp(TIMESTAMP_MIN, TIMESTAMP_MAX);
226 warn!(
228 "found Unix timestamp `{timestamp}` that is outside \
229 Jiff's supported range, clamping to `{clamped}`",
230 );
231 timestamp = clamped;
233 }
234 self.transitions.add(timestamp);
235 }
236 assert!(it.remainder().is_empty());
237 Ok(rest)
238 }
239
240 fn parse_transition_types<'b>(
241 &mut self,
242 header: &Header,
243 bytes: &'b [u8],
244 ) -> Result<&'b [u8], TransitionTypeError> {
245 let (bytes, rest) = try_split_at(
246 SplitAtError::TransitionTypes,
247 bytes,
248 header.transition_types_len(),
249 )?;
250 for (transition_index, &type_index) in (1..).zip(bytes) {
253 if usize::from(type_index) >= header.tzh_typecnt {
254 return Err(TransitionTypeError::ExceedsLocalTimeTypes);
255 }
256 self.transitions.infos[transition_index].type_index = type_index;
257 }
258 Ok(rest)
259 }
260
261 fn parse_local_time_types<'b>(
262 &mut self,
263 header: &Header,
264 bytes: &'b [u8],
265 ) -> Result<&'b [u8], TzifError> {
266 let (bytes, rest) = try_split_at(
267 SplitAtError::LocalTimeTypes,
268 bytes,
269 header.local_time_types_len()?,
270 )?;
271 let mut it = bytes.chunks_exact(6);
272 while let Some(chunk) = it.next() {
273 let offset = from_be_bytes_i32(&chunk[..4]);
274 if !(OFFSET_MIN <= offset && offset <= OFFSET_MAX) {
275 return Err(TzifError::from(
276 LocalTimeTypeError::InvalidOffset { offset },
277 ));
278 }
279 let is_dst = chunk[4] == 1;
280 let designation = (chunk[5], chunk[5]);
281 self.types.push(TzifLocalTimeType {
282 offset,
283 is_dst,
284 designation,
285 indicator: TzifIndicator::LocalWall,
286 });
287 }
288 assert!(it.remainder().is_empty());
289 Ok(rest)
290 }
291
292 fn parse_time_zone_designations<'b>(
293 &mut self,
294 header: &Header,
295 bytes: &'b [u8],
296 ) -> Result<&'b [u8], TimeZoneDesignatorError> {
297 let (bytes, rest) = try_split_at(
298 SplitAtError::TimeZoneDesignations,
299 bytes,
300 header.time_zone_designations_len(),
301 )?;
302 self.fixed.designations = String::from_utf8(bytes.to_vec())
303 .map_err(|_| TimeZoneDesignatorError::InvalidUtf8)?;
304 for typ in self.types.iter_mut() {
306 let start = usize::from(typ.designation.0);
307 let suffix = self
308 .fixed
309 .designations
310 .get(start..)
311 .ok_or(TimeZoneDesignatorError::InvalidStart)?;
312 let len = suffix
313 .find('\x00')
314 .ok_or(TimeZoneDesignatorError::MissingNul)?;
315 let end = start
316 .checked_add(len)
317 .ok_or(TimeZoneDesignatorError::InvalidLength)?;
318 typ.designation.1 = u8::try_from(end)
319 .map_err(|_| TimeZoneDesignatorError::InvalidEnd)?;
320 }
321 Ok(rest)
322 }
323
324 fn parse_leap_seconds<'b>(
329 &mut self,
330 header: &Header,
331 bytes: &'b [u8],
332 ) -> Result<&'b [u8], TzifError> {
333 let (bytes, rest) = try_split_at(
334 SplitAtError::LeapSeconds,
335 bytes,
336 header.leap_second_len()?,
337 )?;
338 let chunk_len = header
339 .time_size
340 .checked_add(4)
341 .expect("time_size plus 4 fits in usize");
342 let mut it = bytes.chunks_exact(chunk_len);
343 while let Some(chunk) = it.next() {
344 let (occur_bytes, _corr_bytes) = chunk.split_at(header.time_size);
345 let occur = if header.is_32bit() {
346 i64::from(from_be_bytes_i32(occur_bytes))
347 } else {
348 from_be_bytes_i64(occur_bytes)
349 };
350 if !(TIMESTAMP_MIN <= occur && occur <= TIMESTAMP_MAX) {
351 warn!(
353 "leap second occurrence `{occur}` is \
354 not in Jiff's supported range"
355 )
356 }
358 }
359 assert!(it.remainder().is_empty());
360 Ok(rest)
361 }
362
363 fn parse_indicators<'b>(
364 &mut self,
365 header: &Header,
366 bytes: &'b [u8],
367 ) -> Result<&'b [u8], IndicatorError> {
368 let (std_wall_bytes, rest) = try_split_at(
369 SplitAtError::StandardWallIndicators,
370 bytes,
371 header.standard_wall_len(),
372 )?;
373 let (ut_local_bytes, rest) = try_split_at(
374 SplitAtError::UTLocalIndicators,
375 rest,
376 header.ut_local_len(),
377 )?;
378 if std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() {
379 if ut_local_bytes.iter().any(|&byte| byte != 0) {
385 return Err(IndicatorError::UtLocalNonZero);
386 }
387 } else if !std_wall_bytes.is_empty() && ut_local_bytes.is_empty() {
388 for (i, &byte) in std_wall_bytes.iter().enumerate() {
389 self.types[i].indicator = if byte == 0 {
392 TzifIndicator::LocalWall
393 } else if byte == 1 {
394 TzifIndicator::LocalStandard
395 } else {
396 return Err(IndicatorError::InvalidStdWallIndicator);
397 };
398 }
399 } else if !std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() {
400 assert_eq!(std_wall_bytes.len(), ut_local_bytes.len());
401 let it = std_wall_bytes.iter().zip(ut_local_bytes);
402 for (i, (&stdwall, &utlocal)) in it.enumerate() {
403 self.types[i].indicator = match (stdwall, utlocal) {
406 (0, 0) => TzifIndicator::LocalWall,
407 (1, 0) => TzifIndicator::LocalStandard,
408 (1, 1) => TzifIndicator::UTStandard,
409 (0, 1) => {
410 return Err(IndicatorError::InvalidUtWallCombination);
411 }
412 _ => return Err(IndicatorError::InvalidCombination),
413 };
414 }
415 } else {
416 debug_assert!(std_wall_bytes.is_empty());
420 debug_assert!(ut_local_bytes.is_empty());
421 }
422 Ok(rest)
423 }
424
425 fn parse_footer<'b>(
426 &mut self,
427 _header: &Header,
428 bytes: &'b [u8],
429 ) -> Result<&'b [u8], FooterError> {
430 if bytes.is_empty() {
431 return Err(FooterError::UnexpectedEnd);
432 }
433 if bytes[0] != b'\n' {
434 return Err(FooterError::MismatchEnd);
435 }
436 let bytes = &bytes[1..];
437 let toscan = &bytes[..bytes.len().min(1024)];
440 let nlat = toscan
441 .iter()
442 .position(|&b| b == b'\n')
443 .ok_or(FooterError::TerminatorNotFound)?;
444 let (bytes, rest) = bytes.split_at(nlat);
445 if !bytes.is_empty() {
446 let posix_tz = PosixTimeZone::parse(bytes)
453 .map_err(FooterError::InvalidPosixTz)?;
454 self.fixed.posix_tz = Some(posix_tz);
455 }
456 Ok(&rest[1..])
457 }
458
459 fn verify_posix_time_zone_consistency(
467 &self,
468 ) -> Result<(), InconsistentPosixTimeZoneError> {
469 if self.transitions.timestamps.len() <= 1 {
475 return Ok(());
476 }
477 let Some(ref tz) = self.fixed.posix_tz else {
478 return Ok(());
479 };
480 let last = self
481 .transitions
482 .timestamps
483 .last()
484 .expect("last transition timestamp");
485 let type_index = self
486 .transitions
487 .infos
488 .last()
489 .expect("last transition info")
490 .type_index;
491 let typ = &self.types[usize::from(type_index)];
492 let (ioff, abbrev, is_dst) =
493 tz.to_offset_info(ITimestamp::from_second(*last));
494 if ioff.second != typ.offset {
495 return Err(InconsistentPosixTimeZoneError::Offset);
496 }
497 if is_dst != typ.is_dst {
498 return Err(InconsistentPosixTimeZoneError::Dst);
499 }
500 if abbrev != self.designation(&typ) {
501 return Err(InconsistentPosixTimeZoneError::Designation);
502 }
503 Ok(())
504 }
505
506 fn add_civil_datetimes_to_transitions(&mut self) {
516 fn to_datetime(timestamp: i64, offset: i32) -> TzifDateTime {
517 let its = ITimestamp { second: timestamp, nanosecond: 0 };
518 let ioff = IOffset { second: offset };
519 let dt = its.to_datetime(ioff);
520 TzifDateTime::new(
521 dt.date.year,
522 dt.date.month,
523 dt.date.day,
524 dt.time.hour,
525 dt.time.minute,
526 dt.time.second,
527 )
528 }
529
530 let trans = &mut self.transitions;
531 trans.infos[0].kind = TzifTransitionKind::Unambiguous;
536 trans.civil_starts[0] = TzifDateTime::MIN;
537 for i in 1..trans.timestamps.len() {
538 let timestamp = trans.timestamps[i];
539 let offset = {
540 let type_index = trans.infos[i].type_index;
541 self.types[usize::from(type_index)].offset
542 };
543 let prev_offset = {
544 let type_index = trans.infos[i.saturating_sub(1)].type_index;
545 self.types[usize::from(type_index)].offset
546 };
547
548 if prev_offset == offset {
549 let start = to_datetime(timestamp, prev_offset);
551 trans.infos[i].kind = TzifTransitionKind::Unambiguous;
552 trans.civil_starts[i] = start;
553 } else if prev_offset < offset {
554 trans.infos[i].kind = TzifTransitionKind::Gap;
560 trans.civil_starts[i] = to_datetime(timestamp, prev_offset);
561 trans.civil_ends[i] = to_datetime(timestamp, offset);
562 } else {
563 assert!(prev_offset > offset);
569 trans.infos[i].kind = TzifTransitionKind::Fold;
570 trans.civil_starts[i] = to_datetime(timestamp, offset);
571 trans.civil_ends[i] = to_datetime(timestamp, prev_offset);
572 }
573 }
574 }
575
576 fn fatten(&mut self) {
582 if !cfg!(feature = "tz-fat") {
585 return;
586 }
587 let Some(posix_tz) = self.fixed.posix_tz.clone() else { return };
588 let last =
589 self.transitions.timestamps.last().expect("last transition");
590 let mut i = 0;
591 let mut prev = ITimestamp::from_second(*last);
592 loop {
593 if i > FATTEN_MAX_TRANSITIONS {
594 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 return;
604 }
605 i += 1;
606 prev = match self.add_transition(&posix_tz, prev) {
607 None => break,
608 Some(next) => next,
609 };
610 }
611 }
612
613 fn add_transition(
616 &mut self,
617 posix_tz: &PosixTimeZone<Abbreviation>,
618 prev: ITimestamp,
619 ) -> Option<ITimestamp> {
620 let (its, ioff, abbrev, is_dst) = posix_tz.next_transition(prev)?;
621 if its.to_datetime(IOffset::UTC).date.year >= FATTEN_UP_TO_YEAR {
622 return None;
623 }
624 let type_index =
625 self.find_or_create_local_time_type(ioff, abbrev, is_dst)?;
626 self.transitions.add_with_type_index(its.second, type_index);
627 Some(its)
628 }
629
630 fn find_or_create_local_time_type(
638 &mut self,
639 offset: IOffset,
640 abbrev: &str,
641 is_dst: bool,
642 ) -> Option<u8> {
643 for (i, typ) in self.types.iter().enumerate() {
644 if offset.second == typ.offset
645 && abbrev == self.designation(typ)
646 && is_dst == typ.is_dst
647 {
648 return u8::try_from(i).ok();
649 }
650 }
651 let i = u8::try_from(self.types.len()).ok()?;
652 let designation = self.find_or_create_designation(abbrev)?;
653 self.types.push(TzifLocalTimeType {
654 offset: offset.second,
655 is_dst,
656 designation,
657 indicator: TzifIndicator::LocalWall,
660 });
661 Some(i)
662 }
663
664 fn find_or_create_designation(
673 &mut self,
674 needle: &str,
675 ) -> Option<(u8, u8)> {
676 let mut start = 0;
677 while let Some(offset) = self.fixed.designations[start..].find('\0') {
678 let end = start + offset;
679 let abbrev = &self.fixed.designations[start..end];
680 if needle == abbrev {
681 return Some((start.try_into().ok()?, end.try_into().ok()?));
682 }
683 start = end + 1;
684 }
685
686 if !self.fixed.designations.ends_with('\0') {
696 self.fixed.designations.push('\0');
697 }
698 let start = self.fixed.designations.len();
699 self.fixed.designations.push_str(needle);
700 self.fixed.designations.push('\0');
701 let end = self.fixed.designations.len();
702 Some((start.try_into().ok()?, end.try_into().ok()?))
703 }
704
705 fn designation(&self, typ: &TzifLocalTimeType) -> &str {
706 let range =
707 usize::from(typ.designation.0)..usize::from(typ.designation.1);
708 &self.fixed.designations[range]
711 }
712}
713
714impl TzifTransitionsOwned {
715 fn add(&mut self, timestamp: i64) {
721 self.add_with_type_index(timestamp, 0);
722 }
723
724 fn add_with_type_index(&mut self, timestamp: i64, type_index: u8) {
727 self.timestamps.push(timestamp);
728 self.civil_starts.push(TzifDateTime::ZERO);
729 self.civil_ends.push(TzifDateTime::ZERO);
730 self.infos.push(TzifTransitionInfo {
731 type_index,
732 kind: TzifTransitionKind::Unambiguous,
733 });
734 }
735}
736
737#[derive(Debug)]
744struct Header {
745 time_size: usize,
750 version: u8,
761 tzh_ttisutcnt: usize,
765 tzh_ttisstdcnt: usize,
769 tzh_leapcnt: usize,
772 tzh_timecnt: usize,
775 tzh_typecnt: usize,
780 tzh_charcnt: usize,
785}
786
787impl Header {
788 fn parse(
795 time_size: usize,
796 bytes: &[u8],
797 ) -> Result<(Header, &[u8]), HeaderError> {
798 assert!(time_size == 4 || time_size == 8, "time size must be 4 or 8");
799 if bytes.len() < 44 {
800 return Err(HeaderError::TooShort);
801 }
802 let (magic, rest) = bytes.split_at(4);
803 if magic != b"TZif" {
804 return Err(HeaderError::MismatchMagic);
805 }
806 let (version, rest) = rest.split_at(1);
807 let (_reserved, rest) = rest.split_at(15);
808
809 let (tzh_ttisutcnt_bytes, rest) = rest.split_at(4);
810 let (tzh_ttisstdcnt_bytes, rest) = rest.split_at(4);
811 let (tzh_leapcnt_bytes, rest) = rest.split_at(4);
812 let (tzh_timecnt_bytes, rest) = rest.split_at(4);
813 let (tzh_typecnt_bytes, rest) = rest.split_at(4);
814 let (tzh_charcnt_bytes, rest) = rest.split_at(4);
815
816 let tzh_ttisutcnt =
817 from_be_bytes_u32_to_usize(tzh_ttisutcnt_bytes).map_err(|e| {
818 HeaderError::ParseCount { kind: CountKind::Ut, convert: e }
819 })?;
820 let tzh_ttisstdcnt =
821 from_be_bytes_u32_to_usize(tzh_ttisstdcnt_bytes).map_err(|e| {
822 HeaderError::ParseCount { kind: CountKind::Std, convert: e }
823 })?;
824 let tzh_leapcnt =
825 from_be_bytes_u32_to_usize(tzh_leapcnt_bytes).map_err(|e| {
826 HeaderError::ParseCount { kind: CountKind::Leap, convert: e }
827 })?;
828 let tzh_timecnt =
829 from_be_bytes_u32_to_usize(tzh_timecnt_bytes).map_err(|e| {
830 HeaderError::ParseCount { kind: CountKind::Time, convert: e }
831 })?;
832 let tzh_typecnt =
833 from_be_bytes_u32_to_usize(tzh_typecnt_bytes).map_err(|e| {
834 HeaderError::ParseCount { kind: CountKind::Type, convert: e }
835 })?;
836 let tzh_charcnt =
837 from_be_bytes_u32_to_usize(tzh_charcnt_bytes).map_err(|e| {
838 HeaderError::ParseCount { kind: CountKind::Char, convert: e }
839 })?;
840
841 if tzh_ttisutcnt != 0 && tzh_ttisutcnt != tzh_typecnt {
842 return Err(HeaderError::MismatchUtType);
843 }
844 if tzh_ttisstdcnt != 0 && tzh_ttisstdcnt != tzh_typecnt {
845 return Err(HeaderError::MismatchStdType);
846 }
847 if tzh_typecnt < 1 {
848 return Err(HeaderError::ZeroType);
849 }
850 if tzh_charcnt < 1 {
851 return Err(HeaderError::ZeroChar);
852 }
853
854 let header = Header {
855 time_size,
856 version: version[0],
857 tzh_ttisutcnt,
858 tzh_ttisstdcnt,
859 tzh_leapcnt,
860 tzh_timecnt,
861 tzh_typecnt,
862 tzh_charcnt,
863 };
864 Ok((header, rest))
865 }
866
867 fn is_32bit(&self) -> bool {
872 self.time_size == 4
873 }
874
875 fn data_block_len(&self) -> Result<usize, HeaderError> {
883 let a = self.transition_times_len()?;
884 let b = self.transition_types_len();
885 let c = self.local_time_types_len()?;
886 let d = self.time_zone_designations_len();
887 let e = self.leap_second_len()?;
888 let f = self.standard_wall_len();
889 let g = self.ut_local_len();
890 a.checked_add(b)
891 .and_then(|z| z.checked_add(c))
892 .and_then(|z| z.checked_add(d))
893 .and_then(|z| z.checked_add(e))
894 .and_then(|z| z.checked_add(f))
895 .and_then(|z| z.checked_add(g))
896 .ok_or(HeaderError::InvalidDataBlock { version: self.version })
897 }
898
899 fn transition_times_len(&self) -> Result<usize, HeaderError> {
900 self.tzh_timecnt
901 .checked_mul(self.time_size)
902 .ok_or(HeaderError::InvalidTimeCount)
903 }
904
905 fn transition_types_len(&self) -> usize {
906 self.tzh_timecnt
907 }
908
909 fn local_time_types_len(&self) -> Result<usize, HeaderError> {
910 self.tzh_typecnt.checked_mul(6).ok_or(HeaderError::InvalidTypeCount)
911 }
912
913 fn time_zone_designations_len(&self) -> usize {
914 self.tzh_charcnt
915 }
916
917 fn leap_second_len(&self) -> Result<usize, HeaderError> {
918 let record_len = self
919 .time_size
920 .checked_add(4)
921 .expect("4-or-8 plus 4 always fits in usize");
922 self.tzh_leapcnt
923 .checked_mul(record_len)
924 .ok_or(HeaderError::InvalidLeapSecondCount)
925 }
926
927 fn standard_wall_len(&self) -> usize {
928 self.tzh_ttisstdcnt
929 }
930
931 fn ut_local_len(&self) -> usize {
932 self.tzh_ttisutcnt
933 }
934}
935
936#[derive(Clone, Debug, Eq, PartialEq)]
937pub(crate) struct TzifError {
938 kind: TzifErrorKind,
939}
940
941#[derive(Clone, Debug, Eq, PartialEq)]
942enum 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")]
956impl std::error::Error for TzifError {}
957
958impl core::fmt::Display for TzifError {
959 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
960 use self::TzifErrorKind::*;
961 match self.kind {
962 Footer(ref err) => {
963 f.write_str("invalid TZif footer: ")?;
964 err.fmt(f)
965 }
966 Header(ref err) => {
967 f.write_str("invalid TZif header: ")?;
968 err.fmt(f)
969 }
970 Header32(ref err) => {
971 f.write_str("invalid 32-bit TZif header: ")?;
972 err.fmt(f)
973 }
974 Header64(ref err) => {
975 f.write_str("invalid 64-bit TZif header: ")?;
976 err.fmt(f)
977 }
978 InconsistentPosixTimeZone(ref err) => {
979 f.write_str(
980 "found inconsistency with \
981 POSIX time zone transition rule \
982 in TZif file footer: ",
983 )?;
984 err.fmt(f)
985 }
986 Indicator(ref err) => {
987 f.write_str("failed to parse indicators: ")?;
988 err.fmt(f)
989 }
990 LocalTimeType(ref err) => {
991 f.write_str("failed to parse local time types: ")?;
992 err.fmt(f)
993 }
994 SplitAt(ref err) => err.fmt(f),
995 TimeZoneDesignator(ref err) => {
996 f.write_str("failed to parse time zone designators: ")?;
997 err.fmt(f)
998 }
999 TransitionType(ref err) => {
1000 f.write_str("failed to parse time zone transition types: ")?;
1001 err.fmt(f)
1002 }
1003 }
1004 }
1005}
1006
1007impl From<TzifErrorKind> for TzifError {
1008 fn from(kind: TzifErrorKind) -> TzifError {
1009 TzifError { kind }
1010 }
1011}
1012
1013#[derive(Clone, Debug, Eq, PartialEq)]
1014enum TransitionTypeError {
1015 ExceedsLocalTimeTypes,
1016 Split(SplitAtError),
1017}
1018
1019impl From<TransitionTypeError> for TzifError {
1020 fn from(err: TransitionTypeError) -> TzifError {
1021 TzifErrorKind::TransitionType(err).into()
1022 }
1023}
1024
1025impl From<SplitAtError> for TransitionTypeError {
1026 fn from(err: SplitAtError) -> TransitionTypeError {
1027 TransitionTypeError::Split(err)
1028 }
1029}
1030
1031impl core::fmt::Display for TransitionTypeError {
1032 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1033 use self::TransitionTypeError::*;
1034
1035 match *self {
1036 ExceedsLocalTimeTypes => f.write_str(
1037 "found time zone transition type index \
1038 that exceeds the number of local time types",
1039 ),
1040 Split(ref err) => err.fmt(f),
1041 }
1042 }
1043}
1044
1045#[derive(Clone, Debug, Eq, PartialEq)]
1046enum LocalTimeTypeError {
1047 InvalidOffset { offset: i32 },
1048 Split(SplitAtError),
1049}
1050
1051impl From<LocalTimeTypeError> for TzifError {
1052 fn from(err: LocalTimeTypeError) -> TzifError {
1053 TzifErrorKind::LocalTimeType(err).into()
1054 }
1055}
1056
1057impl From<SplitAtError> for LocalTimeTypeError {
1058 fn from(err: SplitAtError) -> LocalTimeTypeError {
1059 LocalTimeTypeError::Split(err)
1060 }
1061}
1062
1063impl core::fmt::Display for LocalTimeTypeError {
1064 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1065 use self::LocalTimeTypeError::*;
1066
1067 match *self {
1068 InvalidOffset { offset } => write!(
1069 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 Split(ref err) => err.fmt(f),
1075 }
1076 }
1077}
1078
1079#[derive(Clone, Debug, Eq, PartialEq)]
1080enum TimeZoneDesignatorError {
1081 InvalidEnd,
1082 InvalidLength,
1083 InvalidStart,
1084 InvalidUtf8,
1085 MissingNul,
1086 Split(SplitAtError),
1087}
1088
1089impl From<TimeZoneDesignatorError> for TzifError {
1090 fn from(err: TimeZoneDesignatorError) -> TzifError {
1091 TzifErrorKind::TimeZoneDesignator(err).into()
1092 }
1093}
1094
1095impl From<SplitAtError> for TimeZoneDesignatorError {
1096 fn from(err: SplitAtError) -> TimeZoneDesignatorError {
1097 TimeZoneDesignatorError::Split(err)
1098 }
1099}
1100
1101impl core::fmt::Display for TimeZoneDesignatorError {
1102 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1103 use self::TimeZoneDesignatorError::*;
1104
1105 match *self {
1106 InvalidEnd => f.write_str(
1107 "found invalid end of time zone designator \
1108 for local time type",
1109 ),
1110 InvalidLength => f.write_str(
1111 "found invalid length of time zone designator \
1112 for local time type",
1113 ),
1114 InvalidStart => f.write_str(
1115 "found invalid start of time zone designator \
1116 for local time type",
1117 ),
1118 InvalidUtf8 => {
1119 f.write_str("found invalid UTF-8 in time zone designators")
1120 }
1121 MissingNul => f.write_str(
1122 "could not find NUL terminator for time zone designator",
1123 ),
1124 Split(ref err) => err.fmt(f),
1125 }
1126 }
1127}
1128
1129#[derive(Clone, Debug, Eq, PartialEq)]
1130enum IndicatorError {
1131 InvalidCombination,
1132 InvalidStdWallIndicator,
1133 InvalidUtWallCombination,
1134 Split(SplitAtError),
1135 UtLocalNonZero,
1136}
1137
1138impl From<IndicatorError> for TzifError {
1139 fn from(err: IndicatorError) -> TzifError {
1140 TzifErrorKind::Indicator(err).into()
1141 }
1142}
1143
1144impl From<SplitAtError> for IndicatorError {
1145 fn from(err: SplitAtError) -> IndicatorError {
1146 IndicatorError::Split(err)
1147 }
1148}
1149
1150impl core::fmt::Display for IndicatorError {
1151 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1152 use self::IndicatorError::*;
1153
1154 match *self {
1155 InvalidCombination => f.write_str(
1156 "found invalid std/wall or UT/local value for \
1157 local time type, each must be 0 or 1",
1158 ),
1159 InvalidStdWallIndicator => f.write_str(
1160 "found invalid std/wall indicator, \
1161 expected it to be 0 or 1",
1162 ),
1163 InvalidUtWallCombination => f.write_str(
1164 "found invalid UT-wall combination for \
1165 local time type, only local-wall, \
1166 local-standard and UT-standard are allowed",
1167 ),
1168 Split(ref err) => err.fmt(f),
1169 UtLocalNonZero => f.write_str(
1170 "found non-zero UT/local indicator, \
1171 but all such indicators should be zero",
1172 ),
1173 }
1174 }
1175}
1176
1177#[derive(Clone, Debug, Eq, PartialEq)]
1178enum InconsistentPosixTimeZoneError {
1179 Designation,
1180 Dst,
1181 Offset,
1182}
1183
1184impl From<InconsistentPosixTimeZoneError> for TzifError {
1185 fn from(err: InconsistentPosixTimeZoneError) -> TzifError {
1186 TzifErrorKind::InconsistentPosixTimeZone(err).into()
1187 }
1188}
1189
1190impl core::fmt::Display for InconsistentPosixTimeZoneError {
1191 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1192 use self::InconsistentPosixTimeZoneError::*;
1193
1194 match *self {
1195 Designation => f.write_str(
1196 "expected last transition in TZif file to have \
1197 a time zone abbreviation matching the abbreviation \
1198 derived from the POSIX time zone transition rule",
1199 ),
1200 Dst => f.write_str(
1201 "expected last transition in TZif file to have \
1202 a DST status matching the status derived from the \
1203 POSIX time zone transition rule",
1204 ),
1205 Offset => f.write_str(
1206 "expected last transition in TZif file to have \
1207 DST offset matching the offset derived from the \
1208 POSIX time zone transition rule",
1209 ),
1210 }
1211 }
1212}
1213
1214#[derive(Clone, Debug, Eq, PartialEq)]
1215enum FooterError {
1216 InvalidPosixTz(crate::shared::posix::PosixTimeZoneError),
1217 MismatchEnd,
1218 TerminatorNotFound,
1219 UnexpectedEnd,
1220}
1221
1222impl From<FooterError> for TzifError {
1223 fn from(err: FooterError) -> TzifError {
1224 TzifErrorKind::Footer(err).into()
1225 }
1226}
1227
1228impl core::fmt::Display for FooterError {
1229 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1230 use self::FooterError::*;
1231
1232 match *self {
1233 InvalidPosixTz(ref err) => {
1234 f.write_str("invalid POSIX time zone transition rule")?;
1235 core::fmt::Display::fmt(err, f)
1236 }
1237 MismatchEnd => f.write_str(
1238 "expected to find `\\n` at the beginning of \
1239 the TZif file footer, \
1240 but found something else instead",
1241 ),
1242 TerminatorNotFound => f.write_str(
1243 "expected to find `\\n` terminating \
1244 the TZif file footer, \
1245 but no line terminator could be found",
1246 ),
1247 UnexpectedEnd => f.write_str(
1248 "expected to find `\\n` at the beginning of \
1249 the TZif file footer, \
1250 but found unexpected end of data",
1251 ),
1252 }
1253 }
1254}
1255
1256#[derive(Clone, Debug, Eq, PartialEq)]
1257enum 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
1271impl From<HeaderError> for TzifError {
1272 fn from(err: HeaderError) -> TzifError {
1273 TzifErrorKind::Header(err).into()
1274 }
1275}
1276
1277impl core::fmt::Display for HeaderError {
1278 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1279 use self::HeaderError::*;
1280
1281 match *self {
1282 InvalidDataBlock { version } => write!(
1283 f,
1284 "length of data block in V{version} TZif file is too big",
1285 ),
1286 InvalidLeapSecondCount => {
1287 f.write_str("number of leap seconds is too big")
1288 }
1289 InvalidTimeCount => {
1290 f.write_str("number of transition times is too big")
1291 }
1292 InvalidTypeCount => {
1293 f.write_str("number of local time types is too big")
1294 }
1295 MismatchMagic => f.write_str("magic bytes mismatch"),
1296 MismatchStdType => f.write_str(
1297 "expected number of standard/wall indicators to be zero \
1298 or equal to the number of local time types",
1299 ),
1300 MismatchUtType => f.write_str(
1301 "expected number of UT/local indicators to be zero \
1302 or equal to the number of local time types",
1303 ),
1304 ParseCount { ref kind, ref convert } => {
1305 write!(f, "failed to parse `{kind}`: {convert}")
1306 }
1307 TooShort => f.write_str("too short"),
1308 ZeroChar => f.write_str(
1309 "expected number of time zone abbreviations fo be at least 1",
1310 ),
1311 ZeroType => f.write_str(
1312 "expected number of local time types fo be at least 1",
1313 ),
1314 }
1315 }
1316}
1317
1318#[derive(Clone, Debug, Eq, PartialEq)]
1319enum CountKind {
1320 Ut,
1321 Std,
1322 Leap,
1323 Time,
1324 Type,
1325 Char,
1326}
1327
1328impl core::fmt::Display for CountKind {
1329 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1330 use self::CountKind::*;
1331 match *self {
1332 Ut => f.write_str("tzh_ttisutcnt"),
1333 Std => f.write_str("tzh_ttisstdcnt"),
1334 Leap => f.write_str("tzh_leapcnt"),
1335 Time => f.write_str("tzh_timecnt"),
1336 Type => f.write_str("tzh_typecnt"),
1337 Char => f.write_str("tzh_charcnt"),
1338 }
1339 }
1340}
1341
1342#[derive(Clone, Debug, Eq, PartialEq)]
1343pub(crate) enum SplitAtError {
1344 V1,
1345 LeapSeconds,
1346 LocalTimeTypes,
1347 StandardWallIndicators,
1348 TimeZoneDesignations,
1349 TransitionTimes,
1350 TransitionTypes,
1351 UTLocalIndicators,
1352}
1353
1354impl From<SplitAtError> for TzifError {
1355 fn from(err: SplitAtError) -> TzifError {
1356 TzifErrorKind::SplitAt(err).into()
1357 }
1358}
1359
1360impl core::fmt::Display for SplitAtError {
1361 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1362 use self::SplitAtError::*;
1363
1364 f.write_str("expected bytes for '")?;
1365 f.write_str(match *self {
1366 V1 => "v1 TZif",
1367 LeapSeconds => "leap seconds",
1368 LocalTimeTypes => "local time types",
1369 StandardWallIndicators => "standard/wall indicators",
1370 TimeZoneDesignations => "time zone designations",
1371 TransitionTimes => "transition times",
1372 TransitionTypes => "transition types",
1373 UTLocalIndicators => "UT/local indicators",
1374 })?;
1375 f.write_str("data block', but did not find enough bytes")?;
1376 Ok(())
1377 }
1378}
1379
1380#[derive(Clone, Debug, Eq, PartialEq)]
1381struct U32UsizeError;
1382
1383impl core::fmt::Display for U32UsizeError {
1384 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1385 write!(
1386 f,
1387 "failed to parse integer because it is bigger than `{max}`",
1388 max = usize::MAX,
1389 )
1390 }
1391}
1392
1393fn try_split_at<'b>(
1399 what: SplitAtError,
1400 bytes: &'b [u8],
1401 at: usize,
1402) -> Result<(&'b [u8], &'b [u8]), SplitAtError> {
1403 if at > bytes.len() {
1404 Err(what)
1405 } else {
1406 Ok(bytes.split_at(at))
1407 }
1408}
1409
1410fn from_be_bytes_u32_to_usize(bytes: &[u8]) -> Result<usize, U32UsizeError> {
1422 let n = from_be_bytes_u32(bytes);
1423 usize::try_from(n).map_err(|_| U32UsizeError)
1424}
1425
1426fn from_be_bytes_u32(bytes: &[u8]) -> u32 {
1433 u32::from_be_bytes(bytes.try_into().unwrap())
1434}
1435
1436fn from_be_bytes_i32(bytes: &[u8]) -> i32 {
1443 i32::from_be_bytes(bytes.try_into().unwrap())
1444}
1445
1446fn from_be_bytes_i64(bytes: &[u8]) -> i64 {
1453 i64::from_be_bytes(bytes.try_into().unwrap())
1454}