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