File Coverage

blib/lib/Time/Tzfile.pm
Criterion Covered Total %
statement 92 94 97.8
branch 6 8 75.0
condition 5 9 55.5
subroutine 18 18 100.0
pod 2 14 14.2
total 123 143 86.0


line stmt bran cond sub pod time code
1             package Time::Tzfile;
2             $Time::Tzfile::VERSION = '0.04';
3 1     1   12887 use strict;
  1         2  
  1         23  
4 1     1   4 use warnings;
  1         0  
  1         22  
5              
6 1     1   423 use autodie;
  1         10905  
  1         3  
7 1     1   3500 use Config;
  1         1  
  1         957  
8              
9             #ABSTRACT: read binary tzfiles into Perl data structures
10              
11              
12             sub parse {
13 2     2 1 4 my ($class, $args) = @_;
14              
15 2         3 my $tzdata = parse_raw($class, $args);
16              
17 2         6 my $abbrev = $tzdata->[4][0];
18             # swap null char for pipe so length() works
19 2         6 $abbrev =~ s/\0/|/g;
20              
21 2         3 my @timestamps = ();
22 2         1 for (0..$#{$tzdata->[2]})
  2         7  
23             {
24 487         328 my $struct = $tzdata->[3][ $tzdata->[2][$_] ];
25 487         368 my $abbr_substring = substr $abbrev, $struct->[2];
26 487         471 my ($abbrev, $junk) = split /\|/, $abbr_substring, 2;
27 487         836 push @timestamps, {
28             epoch => $tzdata->[1][$_],
29             offset=> $struct->[0],
30             is_dst=> $struct->[1],
31             type => $abbrev,
32             };
33             }
34 2         21 return \@timestamps;
35             }
36              
37              
38             sub parse_raw {
39 6     6 1 3462 my ($class, $args) = @_;
40              
41 6         15 open my $fh, '<:raw', $args->{filename};
42 6         2069 my $use_version_one = $args->{use_version_one};
43 6         12 my $header = parse_header($fh);
44              
45 6 100 66     106 if ($header->[1] == 2 # it will have the 64 bit entries
      33        
      66        
46             && !$use_version_one # not forcing to 32bit timestamps
47             && ($Config{use64bitint} eq 'define' # Perl is 64bit int capable
48             || $Config{longsize} >= 8)
49             ) {
50              
51             # jump past the version one body
52 3         5 skip_to_next_record($fh, $header);
53              
54             # parse the v2 header
55 3         6 $header = parse_header($fh);
56              
57             return [
58 3         7 $header,
59             parse_time_counts_64($fh, $header),
60             parse_time_type_indices($fh, $header),
61             parse_types($fh, $header),
62             parse_timezone_abbrev($fh, $header),
63             parse_leap_seconds_64($fh, $header),
64             parse_std($fh, $header),
65             parse_gmt($fh, $header),
66             ];
67             }
68             else {
69             return [
70 3         6 $header,
71             parse_time_counts($fh, $header),
72             parse_time_type_indices($fh, $header),
73             parse_types($fh, $header),
74             parse_timezone_abbrev($fh, $header),
75             parse_leap_seconds($fh, $header),
76             parse_std($fh, $header),
77             parse_gmt($fh, $header),
78             ];
79             }
80             }
81              
82             sub parse_bytes (*$@) {
83 54     54 0 49 my ($fh, $bytes_to_read, $template) = @_;
84              
85 54         85 my $bytes_read = read $fh, my($bytes), $bytes_to_read;
86 54 50       2092 die "Expected $bytes_to_read bytes but got $bytes_read"
87             unless $bytes_read == $bytes_to_read;
88              
89 54 100       70 return [] unless $template;
90              
91 45         392 my @data = unpack $template, $bytes;
92 45         188 return \@data;
93             }
94              
95             sub parse_header {
96 9     9 0 7 my ($fh) = @_;
97 9         11 my $header = parse_bytes($fh, 44, 'a4 a x15 N N N N N N');
98              
99 9 50       17 die 'This file does not appear to be a tzfile'
100             if $header->[0] ne 'TZif';
101              
102 9         9 return $header;
103             }
104              
105             sub parse_time_counts {
106 3     3 0 2 my ($fh, $header) = @_;
107 3         4 my $byte_count = 4 * $header->[5];
108 3         7 my $template = 'l>' x $header->[5];
109 3         4 return parse_bytes($fh, $byte_count, $template);
110             }
111              
112             sub parse_time_counts_64 {
113 3     3 0 3 my ($fh, $header) = @_;
114 3         5 my $byte_count = 8 * $header->[5];
115 3         5 my $template = 'q>' x $header->[5];
116 3         4 return parse_bytes($fh, $byte_count, $template);
117             }
118              
119             sub parse_time_type_indices {
120 6     6 0 4 my ($fh, $header) = @_;
121 6         7 my $byte_count = 1 * $header->[5];
122 6         11 my $template = 'C' x $header->[5];
123 6         7 return parse_bytes($fh, $byte_count, $template);
124             }
125              
126             sub parse_types {
127 6     6 0 6 my ($fh, $header) = @_;
128 6         6 my $byte_count = 6 * $header->[6];
129 6         10 my $template = 'l>cC' x $header->[6];
130 6         38 my $data = parse_bytes($fh, $byte_count, $template);
131              
132 6         5 my @mappings = ();
133 6         15 for (my $i = 0; $i < @$data-2; $i += 3) {
134 48         91 push @mappings, [
135             $data->[$i],
136             $data->[$i + 1],
137             $data->[$i + 2],
138             ];
139             }
140 6         14 return \@mappings;
141             }
142              
143             sub parse_timezone_abbrev {
144 6     6 0 5 my ($fh, $header) = @_;
145 6         7 my $byte_count = 1 * $header->[7];
146 6         12 my $template = 'a' . $header->[7];
147 6         7 return parse_bytes($fh, $byte_count, $template);
148             }
149              
150             sub parse_leap_seconds {
151 3     3 0 3 my ($fh, $header) = @_;
152 3         3 my $byte_count = 8 * $header->[4];
153 3         4 my $template = 'l>l>' x $header->[4];
154 3         4 my $data = parse_bytes($fh, $byte_count, $template);
155 3         4 my @mappings = ();
156 3         6 for (my $i = 0; $i < @$data-1; $i += 2) {
157 0         0 push @mappings, {
158             timestamp => $data->[$i],
159             offset => $data->[$i + 1],
160             };
161             }
162 3         7 return \@mappings;
163             }
164              
165             sub parse_leap_seconds_64 {
166 3     3 0 1 my ($fh, $header) = @_;
167 3         4 my $byte_count = 12 * $header->[4];
168 3         4 my $template = 'q>l>' x $header->[4];
169 3         19 my $data = parse_bytes($fh, $byte_count, $template);
170 3         3 my @mappings = ();
171 3         9 for (my $i = 0; $i < @$data-1; $i += 2) {
172 0         0 push @mappings, [
173             $data->[$i],
174             $data->[$i + 1],
175             ];
176             }
177 3         5 return \@mappings;
178             }
179              
180             sub parse_gmt {
181 6     6 0 5 my ($fh, $header) = @_;
182 6         8 my $byte_count = 1 * $header->[2];
183 6         7 my $template = 'c' x $header->[2];
184 6         7 return parse_bytes($fh, $byte_count, $template);
185             }
186              
187             sub parse_std {
188 6     6 0 5 my ($fh, $header) = @_;
189 6         7 my $byte_count = 1 * $header->[3];
190 6         7 my $template = 'c' x $header->[3];
191 6         6 return parse_bytes($fh, $byte_count, $template);
192             }
193              
194             sub skip_to_next_record {
195 3     3 0 4 my ($fh, $header) = @_;
196 3         9 my $bytes_to_skip = 4 * $header->[5]
197             + 1 * $header->[5]
198             + 6 * $header->[6]
199             + 1 * $header->[7]
200             + 8 * $header->[4]
201             + 1 * $header->[2]
202             + 1 * $header->[3];
203 3         4 parse_bytes($fh, $bytes_to_skip);
204             }
205              
206              
207             1;
208              
209             __END__