File Coverage

blib/lib/Time/Tzfile.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


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