1use alloc::{string::String, vec};
2
3use super::{
4 util::{
5 array_str::Abbreviation,
6 error::{err, Error},
7 escape::{Byte, Bytes},
8 itime::{IOffset, ITimestamp},
9 },
10 PosixTimeZone, TzifDateTime, TzifFixed, TzifIndicator, TzifLocalTimeType,
11 TzifOwned, TzifTransitionInfo, TzifTransitionKind, TzifTransitions,
12 TzifTransitionsOwned,
13};
14
15const TIMESTAMP_MIN: i64 = -377705023201;
25const TIMESTAMP_MAX: i64 = 253402207200;
26
27const OFFSET_MIN: i32 = -93599;
40const OFFSET_MAX: i32 = 93599;
41
42const FATTEN_UP_TO_YEAR: i16 = 2038;
47
48const FATTEN_MAX_TRANSITIONS: usize = 300;
62
63impl TzifOwned {
64 pub(crate) fn parse(
79 name: Option<String>,
80 bytes: &[u8],
81 ) -> Result<TzifOwned, Error> {
82 let original = bytes;
83 let name = name.into();
84 let (header32, rest) = Header::parse(4, bytes)
85 .map_err(|e| err!("failed to parse 32-bit header: {e}"))?;
86 let (mut tzif, rest) = if header32.version == 0 {
87 TzifOwned::parse32(name, header32, rest)?
88 } else {
89 TzifOwned::parse64(name, header32, rest)?
90 };
91 tzif.fatten();
92 tzif.add_civil_datetimes_to_transitions();
95 tzif.verify_posix_time_zone_consistency()?;
96 let tzif_raw_len = (rest.as_ptr() as usize)
98 .checked_sub(original.as_ptr() as usize)
99 .unwrap();
100 let tzif_raw_bytes = &original[..tzif_raw_len];
101 tzif.fixed.checksum = super::crc32::sum(tzif_raw_bytes);
102
103 tzif.fixed.designations.shrink_to_fit();
105 tzif.types.shrink_to_fit();
106 tzif.transitions.timestamps.shrink_to_fit();
107 tzif.transitions.civil_starts.shrink_to_fit();
108 tzif.transitions.civil_ends.shrink_to_fit();
109 tzif.transitions.infos.shrink_to_fit();
110
111 Ok(tzif)
112 }
113
114 fn parse32<'b>(
115 name: Option<String>,
116 header32: Header,
117 bytes: &'b [u8],
118 ) -> Result<(TzifOwned, &'b [u8]), Error> {
119 let mut tzif = TzifOwned {
120 fixed: TzifFixed {
121 name,
122 version: header32.version,
123 checksum: 0,
125 designations: String::new(),
126 posix_tz: None,
127 },
128 types: vec![],
129 transitions: TzifTransitions {
130 timestamps: vec![],
131 civil_starts: vec![],
132 civil_ends: vec![],
133 infos: vec![],
134 },
135 };
136 let rest = tzif.parse_transitions(&header32, bytes)?;
137 let rest = tzif.parse_transition_types(&header32, rest)?;
138 let rest = tzif.parse_local_time_types(&header32, rest)?;
139 let rest = tzif.parse_time_zone_designations(&header32, rest)?;
140 let rest = tzif.parse_leap_seconds(&header32, rest)?;
141 let rest = tzif.parse_indicators(&header32, rest)?;
142 Ok((tzif, rest))
143 }
144
145 fn parse64<'b>(
146 name: Option<String>,
147 header32: Header,
148 bytes: &'b [u8],
149 ) -> Result<(TzifOwned, &'b [u8]), Error> {
150 let (_, rest) = try_split_at(
151 "V1 TZif data block",
152 bytes,
153 header32.data_block_len()?,
154 )?;
155 let (header64, rest) = Header::parse(8, rest)
156 .map_err(|e| err!("failed to parse 64-bit header: {e}"))?;
157 let mut tzif = TzifOwned {
158 fixed: TzifFixed {
159 name,
160 version: header64.version,
161 checksum: 0,
163 designations: String::new(),
164 posix_tz: None,
165 },
166 types: vec![],
167 transitions: TzifTransitions {
168 timestamps: vec![],
169 civil_starts: vec![],
170 civil_ends: vec![],
171 infos: vec![],
172 },
173 };
174 let rest = tzif.parse_transitions(&header64, rest)?;
175 let rest = tzif.parse_transition_types(&header64, rest)?;
176 let rest = tzif.parse_local_time_types(&header64, rest)?;
177 let rest = tzif.parse_time_zone_designations(&header64, rest)?;
178 let rest = tzif.parse_leap_seconds(&header64, rest)?;
179 let rest = tzif.parse_indicators(&header64, rest)?;
180 let rest = tzif.parse_footer(&header64, rest)?;
181 Ok((tzif, rest))
185 }
186
187 fn parse_transitions<'b>(
188 &mut self,
189 header: &Header,
190 bytes: &'b [u8],
191 ) -> Result<&'b [u8], Error> {
192 let (bytes, rest) = try_split_at(
193 "transition times data block",
194 bytes,
195 header.transition_times_len()?,
196 )?;
197 let mut it = bytes.chunks_exact(header.time_size);
198 self.transitions.add_with_type_index(TIMESTAMP_MIN, 0);
212 while let Some(chunk) = it.next() {
213 let mut timestamp = if header.is_32bit() {
214 i64::from(from_be_bytes_i32(chunk))
215 } else {
216 from_be_bytes_i64(chunk)
217 };
218 if !(TIMESTAMP_MIN <= timestamp && timestamp <= TIMESTAMP_MAX) {
219 let clamped = timestamp.clamp(TIMESTAMP_MIN, TIMESTAMP_MAX);
231 warn!(
233 "found Unix timestamp {timestamp} that is outside \
234 Jiff's supported range, clamping to {clamped}",
235 );
236 timestamp = clamped;
238 }
239 self.transitions.add(timestamp);
240 }
241 assert!(it.remainder().is_empty());
242 Ok(rest)
243 }
244
245 fn parse_transition_types<'b>(
246 &mut self,
247 header: &Header,
248 bytes: &'b [u8],
249 ) -> Result<&'b [u8], Error> {
250 let (bytes, rest) = try_split_at(
251 "transition types data block",
252 bytes,
253 header.transition_types_len()?,
254 )?;
255 for (transition_index, &type_index) in (1..).zip(bytes) {
258 if usize::from(type_index) >= header.tzh_typecnt {
259 return Err(err!(
260 "found transition type index {type_index},
261 but there are only {} local time types",
262 header.tzh_typecnt,
263 ));
264 }
265 self.transitions.infos[transition_index].type_index = type_index;
266 }
267 Ok(rest)
268 }
269
270 fn parse_local_time_types<'b>(
271 &mut self,
272 header: &Header,
273 bytes: &'b [u8],
274 ) -> Result<&'b [u8], Error> {
275 let (bytes, rest) = try_split_at(
276 "local time types data block",
277 bytes,
278 header.local_time_types_len()?,
279 )?;
280 let mut it = bytes.chunks_exact(6);
281 while let Some(chunk) = it.next() {
282 let offset = from_be_bytes_i32(&chunk[..4]);
283 if !(OFFSET_MIN <= offset && offset <= OFFSET_MAX) {
284 return Err(err!(
285 "found local time type with out-of-bounds offset: {offset}"
286 ));
287 }
288 let is_dst = chunk[4] == 1;
289 let designation = (chunk[5], chunk[5]);
290 self.types.push(TzifLocalTimeType {
291 offset,
292 is_dst,
293 designation,
294 indicator: TzifIndicator::LocalWall,
295 });
296 }
297 assert!(it.remainder().is_empty());
298 Ok(rest)
299 }
300
301 fn parse_time_zone_designations<'b>(
302 &mut self,
303 header: &Header,
304 bytes: &'b [u8],
305 ) -> Result<&'b [u8], Error> {
306 let (bytes, rest) = try_split_at(
307 "time zone designations data block",
308 bytes,
309 header.time_zone_designations_len()?,
310 )?;
311 self.fixed.designations =
312 String::from_utf8(bytes.to_vec()).map_err(|_| {
313 err!(
314 "time zone designations are not valid UTF-8: {:?}",
315 Bytes(bytes),
316 )
317 })?;
318 for (i, typ) in self.types.iter_mut().enumerate() {
320 let start = usize::from(typ.designation.0);
321 let Some(suffix) = self.fixed.designations.get(start..) else {
322 return Err(err!(
323 "local time type {i} has designation index of {start}, \
324 but cannot be more than {}",
325 self.fixed.designations.len(),
326 ));
327 };
328 let Some(len) = suffix.find('\x00') else {
329 return Err(err!(
330 "local time type {i} has designation index of {start}, \
331 but could not find NUL terminator after it in \
332 designations: {:?}",
333 self.fixed.designations,
334 ));
335 };
336 let Some(end) = start.checked_add(len) else {
337 return Err(err!(
338 "local time type {i} has designation index of {start}, \
339 but its length {len} is too big",
340 ));
341 };
342 typ.designation.1 = u8::try_from(end).map_err(|_| {
343 err!(
344 "local time type {i} has designation range of \
345 {start}..{end}, but end is too big",
346 )
347 })?;
348 }
349 Ok(rest)
350 }
351
352 fn parse_leap_seconds<'b>(
357 &mut self,
358 header: &Header,
359 bytes: &'b [u8],
360 ) -> Result<&'b [u8], Error> {
361 let (bytes, rest) = try_split_at(
362 "leap seconds data block",
363 bytes,
364 header.leap_second_len()?,
365 )?;
366 let chunk_len = header
367 .time_size
368 .checked_add(4)
369 .expect("time_size plus 4 fits in usize");
370 let mut it = bytes.chunks_exact(chunk_len);
371 while let Some(chunk) = it.next() {
372 let (occur_bytes, _corr_bytes) = chunk.split_at(header.time_size);
373 let occur = if header.is_32bit() {
374 i64::from(from_be_bytes_i32(occur_bytes))
375 } else {
376 from_be_bytes_i64(occur_bytes)
377 };
378 if !(TIMESTAMP_MIN <= occur && occur <= TIMESTAMP_MAX) {
379 warn!(
381 "leap second occurrence {occur} is \
382 not in Jiff's supported range"
383 )
384 }
386 }
387 assert!(it.remainder().is_empty());
388 Ok(rest)
389 }
390
391 fn parse_indicators<'b>(
392 &mut self,
393 header: &Header,
394 bytes: &'b [u8],
395 ) -> Result<&'b [u8], Error> {
396 let (std_wall_bytes, rest) = try_split_at(
397 "standard/wall indicators data block",
398 bytes,
399 header.standard_wall_len()?,
400 )?;
401 let (ut_local_bytes, rest) = try_split_at(
402 "UT/local indicators data block",
403 rest,
404 header.ut_local_len()?,
405 )?;
406 if std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() {
407 for (i, &byte) in ut_local_bytes.iter().enumerate() {
413 if byte != 0 {
414 return Err(err!(
415 "found UT/local indicator '{byte}' for local time \
416 type {i}, but it must be 0 since all std/wall \
417 indicators are 0",
418 ));
419 }
420 }
421 } else if !std_wall_bytes.is_empty() && ut_local_bytes.is_empty() {
422 for (i, &byte) in std_wall_bytes.iter().enumerate() {
423 self.types[i].indicator = if byte == 0 {
426 TzifIndicator::LocalWall
427 } else if byte == 1 {
428 TzifIndicator::LocalStandard
429 } else {
430 return Err(err!(
431 "found invalid std/wall indicator '{byte}' for \
432 local time type {i}, it must be 0 or 1",
433 ));
434 };
435 }
436 } else if !std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() {
437 assert_eq!(std_wall_bytes.len(), ut_local_bytes.len());
438 let it = std_wall_bytes.iter().zip(ut_local_bytes);
439 for (i, (&stdwall, &utlocal)) in it.enumerate() {
440 self.types[i].indicator = match (stdwall, utlocal) {
443 (0, 0) => TzifIndicator::LocalWall,
444 (1, 0) => TzifIndicator::LocalStandard,
445 (1, 1) => TzifIndicator::UTStandard,
446 (0, 1) => {
447 return Err(err!(
448 "found illegal ut-wall combination for \
449 local time type {i}, only local-wall, \
450 local-standard and ut-standard are allowed",
451 ))
452 }
453 _ => {
454 return Err(err!(
455 "found illegal std/wall or ut/local value for \
456 local time type {i}, each must be 0 or 1",
457 ))
458 }
459 };
460 }
461 } else {
462 debug_assert!(std_wall_bytes.is_empty());
466 debug_assert!(ut_local_bytes.is_empty());
467 }
468 Ok(rest)
469 }
470
471 fn parse_footer<'b>(
472 &mut self,
473 _header: &Header,
474 bytes: &'b [u8],
475 ) -> Result<&'b [u8], Error> {
476 if bytes.is_empty() {
477 return Err(err!(
478 "invalid V2+ TZif footer, expected \\n, \
479 but found unexpected end of data",
480 ));
481 }
482 if bytes[0] != b'\n' {
483 return Err(err!(
484 "invalid V2+ TZif footer, expected {:?}, but found {:?}",
485 Byte(b'\n'),
486 Byte(bytes[0]),
487 ));
488 }
489 let bytes = &bytes[1..];
490 let toscan = &bytes[..bytes.len().min(1024)];
493 let Some(nlat) = toscan.iter().position(|&b| b == b'\n') else {
494 return Err(err!(
495 "invalid V2 TZif footer, could not find {:?} \
496 terminator in: {:?}",
497 Byte(b'\n'),
498 Bytes(toscan),
499 ));
500 };
501 let (bytes, rest) = bytes.split_at(nlat);
502 if !bytes.is_empty() {
503 let posix_tz =
510 PosixTimeZone::parse(bytes).map_err(|e| err!("{e}"))?;
511 self.fixed.posix_tz = Some(posix_tz);
512 }
513 Ok(&rest[1..])
514 }
515
516 fn verify_posix_time_zone_consistency(&self) -> Result<(), Error> {
524 if self.transitions.timestamps.len() <= 1 {
530 return Ok(());
531 }
532 let Some(ref tz) = self.fixed.posix_tz else {
533 return Ok(());
534 };
535 let last = self
536 .transitions
537 .timestamps
538 .last()
539 .expect("last transition timestamp");
540 let type_index = self
541 .transitions
542 .infos
543 .last()
544 .expect("last transition info")
545 .type_index;
546 let typ = &self.types[usize::from(type_index)];
547 let (ioff, abbrev, is_dst) =
548 tz.to_offset_info(ITimestamp::from_second(*last));
549 if ioff.second != typ.offset {
550 return Err(err!(
551 "expected last transition to have DST offset \
552 of {expected_offset}, but got {got_offset} \
553 according to POSIX TZ string {tz}",
554 expected_offset = typ.offset,
555 got_offset = ioff.second,
556 tz = tz,
557 ));
558 }
559 if is_dst != typ.is_dst {
560 return Err(err!(
561 "expected last transition to have is_dst={expected_dst}, \
562 but got is_dst={got_dst} according to POSIX TZ \
563 string {tz}",
564 expected_dst = typ.is_dst,
565 got_dst = is_dst,
566 tz = tz,
567 ));
568 }
569 if abbrev != self.designation(&typ) {
570 return Err(err!(
571 "expected last transition to have \
572 designation={expected_abbrev}, \
573 but got designation={got_abbrev} according to POSIX TZ \
574 string {tz}",
575 expected_abbrev = self.designation(&typ),
576 got_abbrev = abbrev,
577 tz = tz,
578 ));
579 }
580 Ok(())
581 }
582
583 fn add_civil_datetimes_to_transitions(&mut self) {
593 fn to_datetime(timestamp: i64, offset: i32) -> TzifDateTime {
594 use crate::shared::util::itime::{IOffset, ITimestamp};
595 let its = ITimestamp { second: timestamp, nanosecond: 0 };
596 let ioff = IOffset { second: offset };
597 let dt = its.to_datetime(ioff);
598 TzifDateTime::new(
599 dt.date.year,
600 dt.date.month,
601 dt.date.day,
602 dt.time.hour,
603 dt.time.minute,
604 dt.time.second,
605 )
606 }
607
608 let trans = &mut self.transitions;
609 for i in 0..trans.timestamps.len() {
610 let timestamp = trans.timestamps[i];
611 let offset = {
612 let type_index = trans.infos[i].type_index;
613 self.types[usize::from(type_index)].offset
614 };
615 let prev_offset = {
616 let type_index = trans.infos[i.saturating_sub(1)].type_index;
617 self.types[usize::from(type_index)].offset
618 };
619
620 if prev_offset == offset {
621 let start = to_datetime(timestamp, prev_offset);
623 trans.infos[i].kind = TzifTransitionKind::Unambiguous;
624 trans.civil_starts[i] = start;
625 } else if prev_offset < offset {
626 trans.infos[i].kind = TzifTransitionKind::Gap;
632 trans.civil_starts[i] = to_datetime(timestamp, prev_offset);
633 trans.civil_ends[i] = to_datetime(timestamp, offset);
634 } else {
635 assert!(prev_offset > offset);
641 trans.infos[i].kind = TzifTransitionKind::Fold;
642 trans.civil_starts[i] = to_datetime(timestamp, offset);
643 trans.civil_ends[i] = to_datetime(timestamp, prev_offset);
644 }
645 }
646 }
647
648 fn fatten(&mut self) {
654 if !cfg!(feature = "tz-fat") {
657 return;
658 }
659 let Some(posix_tz) = self.fixed.posix_tz.clone() else { return };
660 let last =
661 self.transitions.timestamps.last().expect("last transition");
662 let mut i = 0;
663 let mut prev = ITimestamp::from_second(*last);
664 loop {
665 if i > FATTEN_MAX_TRANSITIONS {
666 warn!(
668 "fattening TZif data for `{name:?}` somehow generated \
669 more than {max} transitions, so giving up to avoid \
670 doing too much work",
671 name = self.fixed.name,
672 max = FATTEN_MAX_TRANSITIONS,
673 );
674 return;
676 }
677 i += 1;
678 prev = match self.add_transition(&posix_tz, prev) {
679 None => break,
680 Some(next) => next,
681 };
682 }
683 }
684
685 fn add_transition(
688 &mut self,
689 posix_tz: &PosixTimeZone<Abbreviation>,
690 prev: ITimestamp,
691 ) -> Option<ITimestamp> {
692 let (its, ioff, abbrev, is_dst) = posix_tz.next_transition(prev)?;
693 if its.to_datetime(IOffset::UTC).date.year >= FATTEN_UP_TO_YEAR {
694 return None;
695 }
696 let type_index =
697 self.find_or_create_local_time_type(ioff, abbrev, is_dst)?;
698 self.transitions.add_with_type_index(its.second, type_index);
699 Some(its)
700 }
701
702 fn find_or_create_local_time_type(
710 &mut self,
711 offset: IOffset,
712 abbrev: &str,
713 is_dst: bool,
714 ) -> Option<u8> {
715 for (i, typ) in self.types.iter().enumerate() {
716 if offset.second == typ.offset
717 && abbrev == self.designation(typ)
718 && is_dst == typ.is_dst
719 {
720 return u8::try_from(i).ok();
721 }
722 }
723 let i = u8::try_from(self.types.len()).ok()?;
724 let designation = self.find_or_create_designation(abbrev)?;
725 self.types.push(TzifLocalTimeType {
726 offset: offset.second,
727 is_dst,
728 designation,
729 indicator: TzifIndicator::LocalWall,
732 });
733 Some(i)
734 }
735
736 fn find_or_create_designation(
745 &mut self,
746 needle: &str,
747 ) -> Option<(u8, u8)> {
748 let mut start = 0;
749 while let Some(offset) = self.fixed.designations[start..].find('\0') {
750 let end = start + offset;
751 let abbrev = &self.fixed.designations[start..end];
752 if needle == abbrev {
753 return Some((start.try_into().ok()?, end.try_into().ok()?));
754 }
755 start = end + 1;
756 }
757 self.fixed.designations.push_str(needle);
758 self.fixed.designations.push('\0');
759 let end = start + needle.len();
760 Some((start.try_into().ok()?, end.try_into().ok()?))
761 }
762
763 fn designation(&self, typ: &TzifLocalTimeType) -> &str {
764 let range =
765 usize::from(typ.designation.0)..usize::from(typ.designation.1);
766 &self.fixed.designations[range]
769 }
770}
771
772impl TzifTransitionsOwned {
773 fn add(&mut self, timestamp: i64) {
779 self.add_with_type_index(timestamp, 0);
780 }
781
782 fn add_with_type_index(&mut self, timestamp: i64, type_index: u8) {
785 self.timestamps.push(timestamp);
786 self.civil_starts.push(TzifDateTime::ZERO);
787 self.civil_ends.push(TzifDateTime::ZERO);
788 self.infos.push(TzifTransitionInfo {
789 type_index,
790 kind: TzifTransitionKind::Unambiguous,
791 });
792 }
793}
794
795#[derive(Debug)]
802struct Header {
803 time_size: usize,
808 version: u8,
819 tzh_ttisutcnt: usize,
823 tzh_ttisstdcnt: usize,
827 tzh_leapcnt: usize,
830 tzh_timecnt: usize,
833 tzh_typecnt: usize,
838 tzh_charcnt: usize,
843}
844
845impl Header {
846 fn parse(
853 time_size: usize,
854 bytes: &[u8],
855 ) -> Result<(Header, &[u8]), Error> {
856 assert!(time_size == 4 || time_size == 8, "time size must be 4 or 8");
857 if bytes.len() < 44 {
858 return Err(err!("invalid header: too short"));
859 }
860 let (magic, rest) = bytes.split_at(4);
861 if magic != b"TZif" {
862 return Err(err!("invalid header: magic bytes mismatch"));
863 }
864 let (version, rest) = rest.split_at(1);
865 let (_reserved, rest) = rest.split_at(15);
866
867 let (tzh_ttisutcnt_bytes, rest) = rest.split_at(4);
868 let (tzh_ttisstdcnt_bytes, rest) = rest.split_at(4);
869 let (tzh_leapcnt_bytes, rest) = rest.split_at(4);
870 let (tzh_timecnt_bytes, rest) = rest.split_at(4);
871 let (tzh_typecnt_bytes, rest) = rest.split_at(4);
872 let (tzh_charcnt_bytes, rest) = rest.split_at(4);
873
874 let tzh_ttisutcnt = from_be_bytes_u32_to_usize(tzh_ttisutcnt_bytes)
875 .map_err(|e| err!("failed to parse tzh_ttisutcnt: {e}"))?;
876 let tzh_ttisstdcnt = from_be_bytes_u32_to_usize(tzh_ttisstdcnt_bytes)
877 .map_err(|e| err!("failed to parse tzh_ttisstdcnt: {e}"))?;
878 let tzh_leapcnt = from_be_bytes_u32_to_usize(tzh_leapcnt_bytes)
879 .map_err(|e| err!("failed to parse tzh_leapcnt: {e}"))?;
880 let tzh_timecnt = from_be_bytes_u32_to_usize(tzh_timecnt_bytes)
881 .map_err(|e| err!("failed to parse tzh_timecnt: {e}"))?;
882 let tzh_typecnt = from_be_bytes_u32_to_usize(tzh_typecnt_bytes)
883 .map_err(|e| err!("failed to parse tzh_typecnt: {e}"))?;
884 let tzh_charcnt = from_be_bytes_u32_to_usize(tzh_charcnt_bytes)
885 .map_err(|e| err!("failed to parse tzh_charcnt: {e}"))?;
886
887 if tzh_ttisutcnt != 0 && tzh_ttisutcnt != tzh_typecnt {
888 return Err(err!(
889 "expected tzh_ttisutcnt={tzh_ttisutcnt} to be zero \
890 or equal to tzh_typecnt={tzh_typecnt}",
891 ));
892 }
893 if tzh_ttisstdcnt != 0 && tzh_ttisstdcnt != tzh_typecnt {
894 return Err(err!(
895 "expected tzh_ttisstdcnt={tzh_ttisstdcnt} to be zero \
896 or equal to tzh_typecnt={tzh_typecnt}",
897 ));
898 }
899 if tzh_typecnt < 1 {
900 return Err(err!(
901 "expected tzh_typecnt={tzh_typecnt} to be at least 1",
902 ));
903 }
904 if tzh_charcnt < 1 {
905 return Err(err!(
906 "expected tzh_charcnt={tzh_charcnt} to be at least 1",
907 ));
908 }
909
910 let header = Header {
911 time_size,
912 version: version[0],
913 tzh_ttisutcnt,
914 tzh_ttisstdcnt,
915 tzh_leapcnt,
916 tzh_timecnt,
917 tzh_typecnt,
918 tzh_charcnt,
919 };
920 Ok((header, rest))
921 }
922
923 fn is_32bit(&self) -> bool {
928 self.time_size == 4
929 }
930
931 fn data_block_len(&self) -> Result<usize, Error> {
939 let a = self.transition_times_len()?;
940 let b = self.transition_types_len()?;
941 let c = self.local_time_types_len()?;
942 let d = self.time_zone_designations_len()?;
943 let e = self.leap_second_len()?;
944 let f = self.standard_wall_len()?;
945 let g = self.ut_local_len()?;
946 a.checked_add(b)
947 .and_then(|z| z.checked_add(c))
948 .and_then(|z| z.checked_add(d))
949 .and_then(|z| z.checked_add(e))
950 .and_then(|z| z.checked_add(f))
951 .and_then(|z| z.checked_add(g))
952 .ok_or_else(|| {
953 err!(
954 "length of data block in V{} tzfile is too big",
955 self.version
956 )
957 })
958 }
959
960 fn transition_times_len(&self) -> Result<usize, Error> {
961 self.tzh_timecnt.checked_mul(self.time_size).ok_or_else(|| {
962 err!("tzh_timecnt value {} is too big", self.tzh_timecnt)
963 })
964 }
965
966 fn transition_types_len(&self) -> Result<usize, Error> {
967 Ok(self.tzh_timecnt)
968 }
969
970 fn local_time_types_len(&self) -> Result<usize, Error> {
971 self.tzh_typecnt.checked_mul(6).ok_or_else(|| {
972 err!("tzh_typecnt value {} is too big", self.tzh_typecnt)
973 })
974 }
975
976 fn time_zone_designations_len(&self) -> Result<usize, Error> {
977 Ok(self.tzh_charcnt)
978 }
979
980 fn leap_second_len(&self) -> Result<usize, Error> {
981 let record_len = self
982 .time_size
983 .checked_add(4)
984 .expect("4-or-8 plus 4 always fits in usize");
985 self.tzh_leapcnt.checked_mul(record_len).ok_or_else(|| {
986 err!("tzh_leapcnt value {} is too big", self.tzh_leapcnt)
987 })
988 }
989
990 fn standard_wall_len(&self) -> Result<usize, Error> {
991 Ok(self.tzh_ttisstdcnt)
992 }
993
994 fn ut_local_len(&self) -> Result<usize, Error> {
995 Ok(self.tzh_ttisutcnt)
996 }
997}
998
999fn try_split_at<'b>(
1005 what: &'static str,
1006 bytes: &'b [u8],
1007 at: usize,
1008) -> Result<(&'b [u8], &'b [u8]), Error> {
1009 if at > bytes.len() {
1010 Err(err!(
1011 "expected at least {at} bytes for {what}, \
1012 but found only {} bytes",
1013 bytes.len(),
1014 ))
1015 } else {
1016 Ok(bytes.split_at(at))
1017 }
1018}
1019
1020fn from_be_bytes_u32_to_usize(bytes: &[u8]) -> Result<usize, Error> {
1032 let n = from_be_bytes_u32(bytes);
1033 usize::try_from(n).map_err(|_| {
1034 err!(
1035 "failed to parse integer {n} (too big, max allowed is {}",
1036 usize::MAX
1037 )
1038 })
1039}
1040
1041fn from_be_bytes_u32(bytes: &[u8]) -> u32 {
1048 u32::from_be_bytes(bytes.try_into().unwrap())
1049}
1050
1051fn from_be_bytes_i32(bytes: &[u8]) -> i32 {
1058 i32::from_be_bytes(bytes.try_into().unwrap())
1059}
1060
1061fn from_be_bytes_i64(bytes: &[u8]) -> i64 {
1068 i64::from_be_bytes(bytes.try_into().unwrap())
1069}