1use super::ensure;
6use super::posix::posix_tz_string;
7use crate::data::posix::PosixTzString;
8use crate::data::time::Seconds;
9use crate::data::tzif::{
10 DataBlock, LeapSecondRecord, LocalTimeTypeRecord, StandardWallIndicator, TzifData, TzifHeader,
11 UtLocalIndicator,
12};
13use combine::parser::byte::byte;
14use combine::parser::byte::num::{be_i32, be_i64, be_u32};
15use combine::{
16 any, between, choice, count_min_max, one_of, skip_count, value, ParseError, Parser, Stream,
17};
18
19fn magic_sequence<Input>() -> impl Parser<Input, Output = u8>
22where
23 Input: Stream<Token = u8>,
24 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
25{
26 byte(b'T')
27 .with(byte(b'Z'))
28 .with(byte(b'i'))
29 .with(byte(b'f'))
30}
31
32fn version<Input>() -> impl Parser<Input, Output = usize>
42where
43 Input: Stream<Token = u8>,
44 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
45{
46 one_of([0, b'2', b'3'])
47 .map(|byte: u8| byte.saturating_sub(b'0') as usize)
48 .map(|version| if version == 0 { 1 } else { version })
49}
50
51fn isutcnt<Input>() -> impl Parser<Input, Output = usize>
56where
57 Input: Stream<Token = u8>,
58 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
59{
60 be_u32().map(|u32| u32 as usize)
61}
62
63fn isstdcnt<Input>() -> impl Parser<Input, Output = usize>
68where
69 Input: Stream<Token = u8>,
70 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
71{
72 be_u32().map(|u32| u32 as usize)
73}
74
75fn leapcnt<Input>() -> impl Parser<Input, Output = usize>
79where
80 Input: Stream<Token = u8>,
81 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
82{
83 be_u32().map(|u32| u32 as usize)
84}
85
86fn timecnt<Input>() -> impl Parser<Input, Output = usize>
90where
91 Input: Stream<Token = u8>,
92 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
93{
94 be_u32().map(|u32| u32 as usize)
95}
96
97fn typecnt<Input>(isutcnt: usize, isstdcnt: usize) -> impl Parser<Input, Output = usize>
108where
109 Input: Stream<Token = u8>,
110 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
111{
112 be_u32()
113 .map(|u32| u32 as usize)
114 .then(|typecnt| {
115 ensure(
116 typecnt,
117 |&typecnt| typecnt != 0,
118 "typecnt should never be equal to zero",
119 )
120 })
121 .then(move |typecnt| {
122 ensure(
123 typecnt,
124 |&typecnt| isutcnt == 0 || isutcnt == typecnt,
125 "if isutcnt is non-zero it should be equal to typecnt",
126 )
127 })
128 .then(move |typecnt| {
129 ensure(
130 typecnt,
131 |&typecnt| isstdcnt == 0 || isstdcnt == typecnt,
132 "if isstdcnt is non-zero it should be equal to typecnt",
133 )
134 })
135}
136
137fn charcnt<Input>() -> impl Parser<Input, Output = usize>
144where
145 Input: Stream<Token = u8>,
146 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
147{
148 be_u32().map(|u32| u32 as usize).then(|charcnt| {
149 ensure(
150 charcnt,
151 |&charcnt| charcnt != 0,
152 "charcnt should never be zero",
153 )
154 })
155}
156
157fn header<Input>() -> impl Parser<Input, Output = TzifHeader>
172where
173 Input: Stream<Token = u8>,
174 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
175{
176 magic_sequence()
177 .with((
178 version(),
179 skip_count(15, any()).with(isutcnt()),
180 isstdcnt(),
181 leapcnt(),
182 timecnt(),
183 ))
184 .then(|(version, isutcnt, isstdcnt, leapcnt, timecnt)| {
185 combine::struct_parser! {
186 TzifHeader {
187 version: value(version),
188 isutcnt: value(isutcnt),
189 isstdcnt: value(isstdcnt),
190 leapcnt: value(leapcnt),
191 timecnt: value(timecnt),
192 typecnt: typecnt(isutcnt, isstdcnt),
193 charcnt: charcnt(),
194 }
195 }
196 })
197}
198
199fn historic_transition_time<const V: usize, Input>() -> impl Parser<Input, Output = Seconds>
207where
208 Input: Stream<Token = u8>,
209 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
210{
211 match V {
212 1 => be_i32().map(i64::from).left(),
213 _ => be_i64().right(),
214 }
215 .then(|time| {
216 ensure(
217 time,
218 |&time| time >= (-2_i64).pow(59),
219 "transition time should not be less than -2.pow(59)",
220 )
221 })
222 .map(Seconds)
223}
224
225fn historic_transition_times<const V: usize, Input>(
229 timecnt: usize,
230) -> impl Parser<Input, Output = Vec<Seconds>>
231where
232 Input: Stream<Token = u8>,
233 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
234{
235 count_min_max(timecnt, timecnt, historic_transition_time::<V, _>()).then(
236 |times: Vec<Seconds>| {
237 ensure(
238 times,
239 |times| {
240 times
241 .iter()
242 .zip(times.iter().skip(1))
243 .all(|(lhs, rhs)| lhs <= rhs)
244 },
245 "historic transition times should be in ascenting order",
246 )
247 },
248 )
249}
250
251fn transition_types<Input>(
259 timecnt: usize,
260 typecnt: usize,
261) -> impl Parser<Input, Output = Vec<usize>>
262where
263 Input: Stream<Token = u8>,
264 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
265{
266 count_min_max(timecnt, timecnt, any().map(|byte| byte as usize)).then(
267 move |types: Vec<usize>| {
268 ensure(
269 types,
270 |types| types.iter().all(|&t| t < typecnt),
271 "all transition types should be in range [0, typecnt - 1]",
272 )
273 },
274 )
275}
276
277fn utoff<Input>() -> impl Parser<Input, Output = Seconds>
286where
287 Input: Stream<Token = u8>,
288 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
289{
290 be_i32()
291 .then(|utoff| {
292 ensure(
293 utoff,
294 |&utoff| utoff != (-2i32).pow(31),
295 "utoff should never be equal to -2.pow(31)",
296 )
297 })
298 .map(|utoff| Seconds(i64::from(utoff)))
299}
300
301fn boolean<Input>() -> impl Parser<Input, Output = bool>
304where
305 Input: Stream<Token = u8>,
306 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
307{
308 choice((byte(b'\x00').map(|_| false), byte(b'\x01').map(|_| true)))
309}
310
311fn is_dst<Input>() -> impl Parser<Input, Output = bool>
317where
318 Input: Stream<Token = u8>,
319 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
320{
321 boolean()
322}
323
324fn idx<Input>(charcnt: usize) -> impl Parser<Input, Output = usize>
333where
334 Input: Stream<Token = u8>,
335 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
336{
337 any()
338 .map(|byte| byte as usize)
339 .then(move |idx| ensure(idx, |&idx| idx < charcnt, "idx should be less than charcnt"))
340}
341
342fn local_time_type_record<Input>(charcnt: usize) -> impl Parser<Input, Output = LocalTimeTypeRecord>
353where
354 Input: Stream<Token = u8>,
355 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
356{
357 combine::struct_parser! {
358 LocalTimeTypeRecord {
359 utoff: utoff(),
360 is_dst: is_dst(),
361 idx: idx(charcnt),
362 }
363 }
364}
365
366fn local_time_type_records<Input>(
369 typecnt: usize,
370 charcnt: usize,
371) -> impl Parser<Input, Output = Vec<LocalTimeTypeRecord>>
372where
373 Input: Stream<Token = u8>,
374 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
375{
376 count_min_max(typecnt, typecnt, local_time_type_record(charcnt))
377}
378
379fn time_zone_designations<Input>(charcnt: usize) -> impl Parser<Input, Output = Vec<String>>
404where
405 Input: Stream<Token = u8>,
406 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
407{
408 count_min_max(charcnt, charcnt, any()).map(|bytes: Vec<u8>| {
409 bytes
410 .split_inclusive(|&b| b == b'\0')
411 .map(|s| String::from_utf8_lossy(&s[0..s.len() - 1]).into_owned())
412 .collect()
413 })
414}
415
416fn leap_second_occurrence<const V: usize, Input>() -> impl Parser<Input, Output = Seconds>
419where
420 Input: Stream<Token = u8>,
421 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
422{
423 match V {
424 1 => be_i32().map(i64::from).left(),
425 _ => be_i64().right(),
426 }
427 .map(Seconds)
428}
429
430fn leap_second_correction<Input>() -> impl Parser<Input, Output = i32>
434where
435 Input: Stream<Token = u8>,
436 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
437{
438 be_i32()
439}
440
441fn leap_second_record<const V: usize, Input>() -> impl Parser<Input, Output = LeapSecondRecord>
465where
466 Input: Stream<Token = u8>,
467 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
468{
469 combine::struct_parser! {
470 LeapSecondRecord {
471 occurrence: leap_second_occurrence::<V, _>(),
472 correction: leap_second_correction(),
473 }
474 }
475}
476
477fn leap_second_records<const V: usize, Input>(
495 leapcnt: usize,
496) -> impl Parser<Input, Output = Vec<LeapSecondRecord>>
497where
498 Input: Stream<Token = u8>,
499 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
500{
501 count_min_max(leapcnt, leapcnt, leap_second_record::<V, _>())
502 .then(|records: Vec<LeapSecondRecord>| {
503 ensure(
504 records,
505 |records| {
506 records
507 .first()
508 .map_or(true, |first| first.occurrence >= Seconds(0))
509 },
510 "The first leap-second occurrence, if present, must be non-negative",
511 )
512 })
513 .then(|records: Vec<LeapSecondRecord>| {
514 ensure(
515 records,
516 |records| {
517 records
518 .first()
519 .map_or(true, |first| first.correction == 1 || first.correction == -1)
520 },
521 "The first leap-second correction, if present, must be 1 or -1",
522 )
523 })
524 .then(|records: Vec<LeapSecondRecord>| {
525 ensure(
526 records,
527 |records| {
528 records
529 .iter()
530 .zip(records.iter().skip(1))
531 .all(|(prev, next)| next.occurrence - prev.occurrence >= Seconds(2_419_199))
532 },
533 "Each subsequent leap-second occurrence must be at least 2419199 greater than the previous value",
534 )
535 })
536 .then(|records: Vec<LeapSecondRecord>| {
537 ensure(
538 records,
539 |records| {
540 records
541 .iter()
542 .zip(records.iter().skip(1))
543 .all(|(prev, next)| (next.correction - prev.correction).abs() == 1)
544 },
545 "Adjacent leap-second corrections must differ by exactly 1",
546 )
547 })
548}
549
550fn standard_wall_indicator<Input>() -> impl Parser<Input, Output = StandardWallIndicator>
557where
558 Input: Stream<Token = u8>,
559 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
560{
561 boolean().map(|bool| {
562 if bool {
563 StandardWallIndicator::Standard
564 } else {
565 StandardWallIndicator::Wall
566 }
567 })
568}
569
570fn standard_wall_indicators<Input>(
576 isstdcnt: usize,
577) -> impl Parser<Input, Output = Vec<StandardWallIndicator>>
578where
579 Input: Stream<Token = u8>,
580 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
581{
582 count_min_max(isstdcnt, isstdcnt, standard_wall_indicator())
583}
584
585fn ut_local_indicator<Input>() -> impl Parser<Input, Output = UtLocalIndicator>
592where
593 Input: Stream<Token = u8>,
594 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
595{
596 boolean().map(|bool| {
597 if bool {
598 UtLocalIndicator::Ut
599 } else {
600 UtLocalIndicator::Local
601 }
602 })
603}
604
605fn ut_local_indicators<Input>(isstdcnt: usize) -> impl Parser<Input, Output = Vec<UtLocalIndicator>>
611where
612 Input: Stream<Token = u8>,
613 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
614{
615 count_min_max(isstdcnt, isstdcnt, ut_local_indicator())
616}
617
618fn data_block<const V: usize, Input>(header: TzifHeader) -> impl Parser<Input, Output = DataBlock>
652where
653 Input: Stream<Token = u8>,
654 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
655{
656 combine::struct_parser! {
657 DataBlock {
658 transition_times: historic_transition_times::<V, _>(header.timecnt),
659 transition_types: transition_types(header.timecnt, header.typecnt),
660 local_time_type_records: local_time_type_records(header.typecnt, header.charcnt),
661 time_zone_designations: time_zone_designations(header.charcnt),
662 leap_second_records: leap_second_records::<V, _>(header.leapcnt),
663 standard_wall_indicators: standard_wall_indicators(header.isstdcnt),
664 ut_local_indicators: ut_local_indicators(header.isutcnt),
665 }
666 }
667}
668
669fn footer<Input>() -> impl Parser<Input, Output = PosixTzString>
706where
707 Input: Stream<Token = u8>,
708 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
709{
710 between(byte(b'\n'), byte(b'\n'), posix_tz_string())
711}
712
713#[must_use]
716pub fn tzif<Input>() -> impl Parser<Input, Output = TzifData>
717where
718 Input: Stream<Token = u8>,
719 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
720{
721 header()
722 .then(|header1| {
723 if header1.version() == 1 {
724 (
725 value(header1),
726 data_block::<1, _>(header1),
727 value(None).left(),
728 )
729 } else {
730 (
731 value(header1),
732 data_block::<1, _>(header1),
733 header().map(Some).right(),
734 )
735 }
736 })
737 .then(|(header1, block1, header2)| match header2 {
738 None => combine::struct_parser! {
739 TzifData {
740 header1: value(header1),
741 data_block1: value(block1),
742 header2: value(header2),
743 data_block2: value(None),
744 footer: value(None),
745 }
746 }
747 .left(),
748 Some(header) => (match header.version() {
749 2 => combine::struct_parser! {
750 TzifData {
751 header1: value(header1),
752 data_block1: value(block1),
753 header2: value(header2),
754 data_block2: data_block::<2, _>(header).map(Some),
755 footer: footer().map(Some),
756 }
757 }
758 .left(),
759 _ => combine::struct_parser! {
760 TzifData {
761 header1: value(header1),
762 data_block1: value(block1),
763 header2: value(header2),
764 data_block2: data_block::<3, _>(header).map(Some),
765 footer: footer().map(Some),
766 }
767 }
768 .right(),
769 })
770 .right(),
771 })
772}
773
774#[cfg(test)]
775mod test {
776 use super::*;
777 use crate::data::posix::{
778 DstTransitionInfo, TimeZoneVariantInfo, TransitionDate, TransitionDay,
779 };
780 use crate::data::time::Hours;
781 use crate::{assert_parse_eq, assert_parse_err, assert_parse_ok};
782 use combine::EasyParser;
783
784 const A: usize = b'A' as usize;
786 const B: usize = b'B' as usize;
787 const C: usize = b'C' as usize;
788 const D: usize = b'D' as usize;
789
790 #[test]
791 fn parse_magic_sequence() {
792 assert_parse_err!(magic_sequence(), "");
794
795 assert_parse_err!(magic_sequence(), "asdf");
797 assert_parse_err!(magic_sequence(), "tzif");
798 assert_parse_err!(magic_sequence(), "TZIF");
799
800 assert_parse_ok!(magic_sequence(), "TZif");
802 }
803
804 #[test]
805 fn parse_version() {
806 assert_parse_err!(version(), "");
808
809 assert_parse_err!(version(), "0");
811 assert_parse_err!(version(), "1");
812 assert_parse_err!(version(), "4");
813
814 assert_parse_eq!(version(), "\x00", 1);
816 assert_parse_eq!(version(), "2", 2);
817 assert_parse_eq!(version(), "3", 3);
818 }
819
820 #[test]
821 fn parse_trivial_count_values() {
822 assert_parse_err!(isutcnt(), "");
824 assert_parse_err!(isstdcnt(), "");
825 assert_parse_err!(leapcnt(), "");
826 assert_parse_err!(timecnt(), "");
827
828 assert_parse_err!(isutcnt(), "\x00");
830 assert_parse_err!(isutcnt(), "\x00\x00");
831 assert_parse_err!(isutcnt(), "\x00\x00\x00");
832
833 assert_parse_err!(isstdcnt(), "\x00");
834 assert_parse_err!(isstdcnt(), "\x00\x00");
835 assert_parse_err!(isstdcnt(), "\x00\x00\x00");
836
837 assert_parse_err!(leapcnt(), "\x00");
838 assert_parse_err!(leapcnt(), "\x00\x00");
839 assert_parse_err!(leapcnt(), "\x00\x00\x00");
840
841 assert_parse_err!(timecnt(), "\x00");
842 assert_parse_err!(timecnt(), "\x00\x00");
843 assert_parse_err!(timecnt(), "\x00\x00\x00");
844
845 assert_parse_eq!(isutcnt(), "\x00\x00\x00\x41", A);
847 assert_parse_eq!(isutcnt(), "\x00\x00\x00\x00", 0);
848
849 assert_parse_eq!(isstdcnt(), "\x00\x00\x00\x42", B);
850 assert_parse_eq!(isstdcnt(), "\x00\x00\x00\x00", 0);
851
852 assert_parse_eq!(leapcnt(), "\x00\x00\x00\x43", C);
853 assert_parse_eq!(leapcnt(), "\x00\x00\x00\x00", 0);
854
855 assert_parse_eq!(timecnt(), "\x00\x00\x00\x44", D);
856 assert_parse_eq!(timecnt(), "\x00\x00\x00\x00", 0);
857 }
858
859 #[test]
860 fn parse_typecnt() {
861 assert_parse_err!(typecnt(0, 0), "");
863
864 assert_parse_err!(typecnt(0, 0), "\x00");
866 assert_parse_err!(typecnt(0, 0), "\x00\x00");
867 assert_parse_err!(typecnt(0, 0), "\x00\x00\x00");
868
869 assert_parse_err!(typecnt(0, 0), "\x00\x00\x00\x00");
871
872 assert_parse_err!(typecnt(B, 0), "\x00\x00\x00\x41");
874 assert_parse_err!(typecnt(0, B), "\x00\x00\x00\x41");
875 assert_parse_err!(typecnt(A, B), "\x00\x00\x00\x41");
876 assert_parse_err!(typecnt(B, A), "\x00\x00\x00\x41");
877
878 assert_parse_eq!(typecnt(0, 0), "\x00\x00\x00\x41", A);
880 assert_parse_eq!(typecnt(B, B), "\x00\x00\x00\x42", B);
881 }
882
883 #[test]
884 fn parse_charcnt() {
885 assert_parse_err!(charcnt(), "");
887
888 assert_parse_err!(charcnt(), "\x00");
890 assert_parse_err!(charcnt(), "\x00\x00");
891 assert_parse_err!(charcnt(), "\x00\x00\x00");
892
893 assert_parse_err!(charcnt(), "\x00\x00\x00\x00");
895
896 assert_parse_eq!(charcnt(), "\x00\x00\x00\x41", A);
898 assert_parse_eq!(charcnt(), "\x00\x00\x00\x42", B);
899 }
900
901 #[test]
902 fn parse_header() {
903 assert_parse_err!(header(), "");
905
906 assert_parse_err!(
908 header(),
909 "TZif1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0",
910 );
911
912 assert_parse_err!(
914 header(),
915 "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0",
916 );
917
918 assert_parse_err!(
920 header(),
921 "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0",
922 );
923
924 assert_parse_err!(
926 header(),
927 "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0",
928 );
929
930 assert_parse_err!(
932 header(),
933 "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0",
934 );
935
936 assert_parse_eq!(
938 header(),
939 "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0",
940 TzifHeader {
941 version: 2,
942 isutcnt: 0,
943 isstdcnt: 0,
944 leapcnt: 0,
945 timecnt: 0,
946 typecnt: 1,
947 charcnt:1,
948 }
949 );
950 }
951
952 #[test]
953 fn parse_historic_transition_time() {
954 const ONE: &[u8] = 1i64.to_be_bytes().as_slice();
955 const AT_BOUNDARY: &[u8] = (-2_i64).pow(59).to_be_bytes().as_slice();
956 const OUT_OF_BOUNDS: &[u8] = ((-2_i64).pow(59) - 1).to_be_bytes().as_slice();
957
958 assert_parse_err!(
960 historic_transition_time::<2, _>(),
961 bytes OUT_OF_BOUNDS,
962 );
963
964 assert_parse_eq!(
966 historic_transition_time::<2, _>(),
967 bytes ONE,
968 Seconds(1),
969 );
970
971 assert_parse_eq!(
974 historic_transition_time::<1, _>(),
975 bytes ONE,
976 Seconds(0),
977 );
978
979 assert_parse_eq!(
982 historic_transition_time::<1, _>(),
983 bytes ONE[ONE.len() / 2..].as_ref(),
984 Seconds(1),
985 );
986
987 assert_parse_eq!(
988 historic_transition_time::<3, _>(),
989 bytes AT_BOUNDARY,
990 Seconds((-2_i64).pow(59)),
991 );
992 }
993
994 #[test]
995 fn parse_historic_transition_times() {
996 const ONE: &[u8] = 1i64.to_be_bytes().as_slice();
997 const TWO: &[u8] = 2i64.to_be_bytes().as_slice();
998 const SIX: &[u8] = 6i64.to_be_bytes().as_slice();
999 let ascending = ONE
1000 .iter()
1001 .chain(TWO)
1002 .chain(SIX)
1003 .copied()
1004 .collect::<Vec<u8>>();
1005 let descending = SIX
1006 .iter()
1007 .chain(TWO)
1008 .chain(ONE)
1009 .copied()
1010 .collect::<Vec<u8>>();
1011
1012 assert_parse_err!(
1014 historic_transition_times::<2, _>(3),
1015 bytes descending.as_slice(),
1016 );
1017
1018 assert_parse_err!(
1020 historic_transition_times::<2, _>(4),
1021 bytes ascending.as_slice(),
1022 );
1023
1024 assert_parse_eq!(
1026 historic_transition_times::<2, _>(3),
1027 bytes ascending.as_slice(),
1028 vec![
1029 Seconds(1),
1030 Seconds(2),
1031 Seconds(6),
1032 ]
1033 );
1034 }
1035
1036 #[test]
1037 fn parse_transition_types() {
1038 assert_parse_err!(transition_types(3, 3), "");
1040
1041 assert_parse_err!(transition_types(3, 3), "\x00\x01");
1043
1044 assert_parse_err!(transition_types(3, 3), "\x00\x01\x03");
1046
1047 assert_parse_eq!(transition_types(3, 3), "\x00\x01\x02", vec![0, 1, 2],);
1049 assert_parse_eq!(transition_types(3, 3), "\x02\x01\x01", vec![2, 1, 1],);
1050 }
1051
1052 #[test]
1053 fn parse_utoff() {
1054 const ONE: &[u8] = 1i32.to_be_bytes().as_slice();
1055 const TWO: &[u8] = 2i32.to_be_bytes().as_slice();
1056 const SIX: &[u8] = 6i32.to_be_bytes().as_slice();
1057 const INVALID: &[u8] = ((-2i32).pow(31)).to_be_bytes().as_slice();
1058
1059 assert_parse_err!(utoff(), bytes INVALID);
1061
1062 assert_parse_eq!(utoff(), bytes ONE, Seconds(1));
1064 assert_parse_eq!(utoff(), bytes TWO, Seconds(2));
1065 assert_parse_eq!(utoff(), bytes SIX, Seconds(6));
1066 }
1067
1068 #[test]
1069 fn parse_is_dst() {
1070 assert_parse_err!(is_dst(), "");
1072
1073 assert_parse_err!(is_dst(), "0");
1075 assert_parse_err!(is_dst(), "1");
1076
1077 assert_parse_eq!(is_dst(), "\x00", false);
1079 assert_parse_eq!(is_dst(), "\x01", true);
1080 }
1081
1082 #[test]
1083 fn parse_idx() {
1084 assert_parse_err!(idx(0), "");
1086
1087 assert_parse_err!(idx(3), "\x03");
1089
1090 assert_parse_eq!(idx(3), "\x00", 0);
1092 assert_parse_eq!(idx(3), "\x01", 1);
1093 assert_parse_eq!(idx(3), "\x02", 2);
1094 }
1095
1096 #[test]
1097 fn parse_local_time_type_record() {
1098 assert_parse_eq!(
1099 local_time_type_record(3),
1100 "\x00\x00\x00\x10\x01\x02",
1101 LocalTimeTypeRecord {
1102 utoff: Seconds(16),
1103 is_dst: true,
1104 idx: 2
1105 }
1106 );
1107 assert_parse_eq!(
1108 local_time_type_record(3),
1109 "\x00\x00\x10\x10\x00\x01",
1110 LocalTimeTypeRecord {
1111 utoff: Seconds(16 * 16 * 16 + 16),
1112 is_dst: false,
1113 idx: 1,
1114 }
1115 );
1116 }
1117
1118 #[test]
1119 fn parse_local_time_type_records() {
1120 assert_parse_err!(
1122 local_time_type_records(3, 3),
1123 "\x00\x00\x00\x10\x01\x02\x00\x00\x10\x10\x00\x01",
1124 );
1125
1126 assert_parse_eq!(
1128 local_time_type_records(2, 3),
1129 "\x00\x00\x00\x10\x01\x02\x00\x00\x10\x10\x00\x01",
1130 vec![
1131 LocalTimeTypeRecord {
1132 utoff: Seconds(16),
1133 is_dst: true,
1134 idx: 2
1135 },
1136 LocalTimeTypeRecord {
1137 utoff: Seconds(16 * 16 * 16 + 16),
1138 is_dst: false,
1139 idx: 1,
1140 },
1141 ]
1142 );
1143 }
1144
1145 #[test]
1146 fn parse_time_zone_designations() {
1147 assert_parse_eq!(
1148 time_zone_designations(14),
1149 "LMT\0AEDT\0AEST\0",
1150 vec!["LMT".to_owned(), "AEDT".to_owned(), "AEST".to_owned()],
1151 );
1152 }
1153
1154 #[test]
1155 fn time_zone_designation_indexing() {
1156 let block: &[u8] = &[
1157 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x03, 0x00, 0x00, 0x00, 0x10, 0x01, 0x04, 0x00, 0x00, 0x00, 0x10, 0x01, 0x05, b'L', b'M', b'T', 0x00, b'A', b'E', b'D', b'T', 0x00, ];
1163 let header = TzifHeader {
1164 version: 0,
1165 isutcnt: 0,
1166 isstdcnt: 0,
1167 leapcnt: 0,
1168 timecnt: 0,
1169 typecnt: 4,
1170 charcnt: 9,
1171 };
1172 let (block, _) = data_block::<1, _>(header).parse(block).unwrap();
1173 assert_eq!(
1174 block.time_zone_designation(block.local_time_type_records[0].idx),
1175 Some("LMT")
1176 );
1177 assert_eq!(
1178 block.time_zone_designation(block.local_time_type_records[1].idx),
1179 Some("")
1180 );
1181 assert_eq!(
1182 block.time_zone_designation(block.local_time_type_records[2].idx),
1183 Some("AEDT")
1184 );
1185 assert_eq!(
1186 block.time_zone_designation(block.local_time_type_records[3].idx),
1187 Some("EDT")
1188 );
1189 assert_eq!(block.time_zone_designation(8), Some(""));
1190 assert_eq!(block.time_zone_designation(9), None);
1191 }
1192
1193 #[test]
1194 fn parse_leap_second_occurrence() {
1195 const FIVE: &[u8] = 5i64.to_be_bytes().as_slice();
1196
1197 assert_parse_eq!(
1200 historic_transition_time::<1, _>(),
1201 bytes FIVE,
1202 Seconds(0),
1203 );
1204
1205 assert_parse_eq!(
1208 historic_transition_time::<1, _>(),
1209 bytes FIVE[FIVE.len() / 2..].as_ref(),
1210 Seconds(5),
1211 );
1212
1213 assert_parse_eq!(
1215 historic_transition_time::<2, _>(),
1216 bytes FIVE,
1217 Seconds(5),
1218 );
1219
1220 assert_parse_eq!(
1222 historic_transition_time::<3, _>(),
1223 bytes FIVE,
1224 Seconds(5),
1225 );
1226 }
1227
1228 #[test]
1229 fn parse_leap_second_record() {
1230 const ONE_64BIT: &[u8] = 1i64.to_be_bytes().as_slice();
1231 const ONE_32BIT: &[u8] = 1i32.to_be_bytes().as_slice();
1232
1233 let record_v1 = ONE_32BIT
1234 .iter()
1235 .chain(ONE_32BIT)
1236 .copied()
1237 .collect::<Vec<u8>>();
1238 let record_v2p = ONE_64BIT
1239 .iter()
1240 .chain(ONE_32BIT)
1241 .copied()
1242 .collect::<Vec<u8>>();
1243
1244 assert_parse_eq!(
1246 leap_second_record::<1, _>(),
1247 bytes record_v1.as_slice(),
1248 LeapSecondRecord {
1249 occurrence: Seconds(1),
1250 correction: 1,
1251 }
1252 );
1253
1254 assert_parse_eq!(
1256 leap_second_record::<2, _>(),
1257 bytes record_v2p.as_slice(),
1258 LeapSecondRecord {
1259 occurrence: Seconds(1),
1260 correction: 1,
1261 }
1262 );
1263
1264 assert_parse_eq!(
1266 leap_second_record::<3, _>(),
1267 bytes record_v2p.as_slice(),
1268 LeapSecondRecord {
1269 occurrence: Seconds(1),
1270 correction: 1,
1271 }
1272 );
1273 }
1274
1275 #[test]
1276 fn parse_leap_second_records() {
1277 let invalid_first_occurrence = (-5i64)
1278 .to_be_bytes()
1279 .iter()
1280 .copied()
1281 .chain(1i32.to_be_bytes().iter().copied())
1282 .collect::<Vec<u8>>();
1283 let invalid_first_correction = 0i64
1284 .to_be_bytes()
1285 .iter()
1286 .copied()
1287 .chain(0i32.to_be_bytes().iter().copied())
1288 .collect::<Vec<u8>>();
1289 let invalid_second_occurrence = 0i64
1290 .to_be_bytes()
1291 .iter()
1292 .copied()
1293 .chain(1i32.to_be_bytes().iter().copied())
1294 .chain(2419198i64.to_be_bytes().iter().copied())
1295 .chain(2i32.to_be_bytes().iter().copied())
1296 .chain((2 * 2419199i64).to_be_bytes().iter().copied())
1297 .chain(3i32.to_be_bytes().iter().copied())
1298 .collect::<Vec<u8>>();
1299 let invalid_second_correction = 0i64
1300 .to_be_bytes()
1301 .iter()
1302 .copied()
1303 .chain(1i32.to_be_bytes().iter().copied())
1304 .chain(2419199i64.to_be_bytes().iter().copied())
1305 .chain(3i32.to_be_bytes().iter().copied())
1306 .chain((2 * 2419199i64).to_be_bytes().iter().copied())
1307 .chain(4i32.to_be_bytes().iter().copied())
1308 .collect::<Vec<u8>>();
1309 let valid_v1 = 0i32
1310 .to_be_bytes()
1311 .iter()
1312 .copied()
1313 .chain(1i32.to_be_bytes().iter().copied())
1314 .chain(2419199i32.to_be_bytes().iter().copied())
1315 .chain(2i32.to_be_bytes().iter().copied())
1316 .chain((2 * 2419199i32).to_be_bytes().iter().copied())
1317 .chain(3i32.to_be_bytes().iter().copied())
1318 .collect::<Vec<u8>>();
1319 let valid_v2p = 0i64
1320 .to_be_bytes()
1321 .iter()
1322 .copied()
1323 .chain(1i32.to_be_bytes().iter().copied())
1324 .chain(2419199i64.to_be_bytes().iter().copied())
1325 .chain(2i32.to_be_bytes().iter().copied())
1326 .chain((2 * 2419199i64).to_be_bytes().iter().copied())
1327 .chain(3i32.to_be_bytes().iter().copied())
1328 .collect::<Vec<u8>>();
1329
1330 assert_parse_err!(
1332 leap_second_records::<2, _>(4),
1333 bytes valid_v2p.as_slice(),
1334 );
1335
1336 assert_parse_err!(
1338 leap_second_records::<2, _>(1),
1339 bytes invalid_first_correction.as_slice(),
1340 );
1341
1342 assert_parse_err!(
1343 leap_second_records::<2, _>(1),
1344 bytes invalid_first_occurrence.as_slice(),
1345 );
1346
1347 assert_parse_err!(
1348 leap_second_records::<2, _>(2),
1349 bytes invalid_second_correction.as_slice(),
1350 );
1351
1352 assert_parse_err!(
1353 leap_second_records::<2, _>(2),
1354 bytes invalid_second_occurrence.as_slice(),
1355 );
1356
1357 assert_parse_eq!(
1359 leap_second_records::<1, _>(2),
1360 bytes valid_v1.as_slice(),
1361 vec![
1362 LeapSecondRecord {
1363 occurrence: Seconds(0),
1364 correction: 1,
1365 },
1366 LeapSecondRecord {
1367 occurrence: Seconds(2419199),
1368 correction: 2,
1369 },
1370 ],
1371 );
1372
1373 assert_parse_eq!(
1374 leap_second_records::<2, _>(2),
1375 bytes valid_v2p.as_slice(),
1376 vec![
1377 LeapSecondRecord {
1378 occurrence: Seconds(0),
1379 correction: 1,
1380 },
1381 LeapSecondRecord {
1382 occurrence: Seconds(2419199),
1383 correction: 2,
1384 },
1385 ],
1386 );
1387 }
1388
1389 #[test]
1390 fn parse_standard_wall_indicators() {
1391 assert_parse_err!(standard_wall_indicators(3), "");
1393
1394 assert_parse_err!(standard_wall_indicators(3), "\x00\x01");
1396
1397 assert_parse_err!(standard_wall_indicators(3), "\x00\x01\x02");
1399
1400 assert_parse_eq!(
1402 standard_wall_indicators(0),
1403 "",
1404 Vec::<StandardWallIndicator>::new()
1405 );
1406 assert_parse_eq!(
1407 standard_wall_indicators(4),
1408 "\x00\x01\x01\x00",
1409 vec![
1410 StandardWallIndicator::Wall,
1411 StandardWallIndicator::Standard,
1412 StandardWallIndicator::Standard,
1413 StandardWallIndicator::Wall,
1414 ]
1415 );
1416 }
1417
1418 #[test]
1419 fn parse_ut_local_indicators() {
1420 assert_parse_err!(ut_local_indicators(3), "");
1422
1423 assert_parse_err!(ut_local_indicators(3), "\x00\x01");
1425
1426 assert_parse_err!(ut_local_indicators(3), "\x00\x01\x02");
1428
1429 assert_parse_eq!(ut_local_indicators(0), "", Vec::<UtLocalIndicator>::new());
1431 assert_parse_eq!(
1432 ut_local_indicators(4),
1433 "\x01\x00\x00\x01",
1434 vec![
1435 UtLocalIndicator::Ut,
1436 UtLocalIndicator::Local,
1437 UtLocalIndicator::Local,
1438 UtLocalIndicator::Ut,
1439 ]
1440 );
1441 }
1442
1443 #[test]
1444 fn parse_footer() {
1445 assert_parse_err!(footer(), "EST+5EDT,M3.2.0/2,M11.1.0/2\n");
1447
1448 assert_parse_err!(footer(), "\nEST+5EDT,M3.2.0/2,M11.1.0/2");
1450
1451 assert_parse_eq!(
1453 footer(),
1454 "\nEST+5EDT,M3.2.0/2,M11.1.0/2\n",
1455 PosixTzString {
1456 std_info: TimeZoneVariantInfo {
1457 name: "EST".to_owned(),
1458 offset: Hours(5).as_seconds(),
1459 },
1460 dst_info: Some(DstTransitionInfo {
1461 variant_info: TimeZoneVariantInfo {
1462 name: "EDT".to_owned(),
1463 offset: Hours(4).as_seconds()
1464 },
1465 start_date: TransitionDate {
1466 day: TransitionDay::Mwd(3, 2, 0),
1467 time: Hours(2).as_seconds(),
1468 },
1469 end_date: TransitionDate {
1470 day: TransitionDay::Mwd(11, 1, 0),
1471 time: Hours(2).as_seconds(),
1472 },
1473 })
1474 }
1475 );
1476 }
1477}