File Coverage

blib/lib/HTTP/Date.pm
Criterion Covered Total %
statement 66 71 92.9
branch 45 60 75.0
condition 25 25 100.0
subroutine 6 6 100.0
pod 5 5 100.0
total 147 167 88.0


line stmt bran cond sub pod time code
1             package HTTP::Date;
2              
3 1     1   5169 use strict;
  1         9  
  1         1845  
4              
5             our $VERSION = '6.05';
6              
7             require Exporter;
8             our @ISA = qw(Exporter);
9             our @EXPORT = qw(time2str str2time);
10             our @EXPORT_OK = qw(parse_date time2iso time2isoz);
11              
12             require Time::Local;
13              
14             our ( @DoW, @MoY, %MoY );
15             @DoW = qw(Sun Mon Tue Wed Thu Fri Sat);
16             @MoY = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
17             @MoY{@MoY} = ( 1 .. 12 );
18              
19             my %GMT_ZONE = ( GMT => 1, UTC => 1, UT => 1, Z => 1 );
20              
21             sub time2str (;$) {
22 1     1 1 198 my $time = shift;
23 1 50       5 $time = time unless defined $time;
24 1         144 my ( $sec, $min, $hour, $mday, $mon, $year, $wday ) = gmtime($time);
25 1         23 sprintf(
26             "%s, %02d %s %04d %02d:%02d:%02d GMT",
27             $DoW[$wday],
28             $mday, $MoY[$mon], $year + 1900,
29             $hour, $min, $sec
30             );
31             }
32              
33             sub str2time ($;$) {
34 115     115 1 11152 my $str = shift;
35 115 100       370 return undef unless defined $str;
36              
37             # fast exit for strictly conforming string
38 114 100       449 if ( $str
39             =~ /^[SMTWF][a-z][a-z], (\d\d) ([JFMAJSOND][a-z][a-z]) (\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$/
40             ) {
41 1         3 return eval {
42 1         6 my $t = Time::Local::timegm( $6, $5, $4, $1, $MoY{$2} - 1, $3 );
43 1 50       40 $t < 0 ? undef : $t;
44             };
45             }
46              
47 113         258 my @d = parse_date($str);
48 113 100       306 return undef unless @d;
49 107         155 $d[1]--; # month
50              
51 107         222 my $tz = pop(@d);
52 107 100       183 unless ( defined $tz ) {
53 51 100       164 unless ( defined( $tz = shift ) ) {
54 15         26 return eval {
55 15         24 my $frac = $d[-1];
56 15         30 $frac -= ( $d[-1] = int($frac) );
57 15         74 my $t = Time::Local::timelocal( reverse @d ) + $frac;
58 10 50       1099 $t < 0 ? undef : $t;
59             };
60             }
61             }
62              
63 92         127 my $offset = 0;
64 92 100       384 if ( $GMT_ZONE{ uc $tz } ) {
    50          
65              
66             # offset already zero
67             }
68             elsif ( $tz =~ /^([-+])?(\d\d?):?(\d\d)?$/ ) {
69 27         79 $offset = 3600 * $2;
70 27 50       74 $offset += 60 * $3 if $3;
71 27 100 100     113 $offset *= -1 if $1 && $1 eq '-';
72             }
73             else {
74 0 0       0 eval { require Time::Zone } || return undef;
  0         0  
75 0         0 $offset = Time::Zone::tz_offset($tz);
76 0 0       0 return undef unless defined $offset;
77             }
78              
79 92         143 return eval {
80 92         122 my $frac = $d[-1];
81 92         270 $frac -= ( $d[-1] = int($frac) );
82 92         267 my $t = Time::Local::timegm( reverse @d ) + $frac;
83 92 50       3517 $t < 0 ? undef : $t - $offset;
84             };
85             }
86              
87             sub parse_date ($) {
88 116     116 1 632 local ($_) = shift;
89 116 50       277 return unless defined;
90              
91             # More lax parsing below
92 116         526 s/^\s+//; # kill leading space
93 116         650 s/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*//i; # Useless weekday
94              
95 116         215 my ( $day, $mon, $yr, $hr, $min, $sec, $tz, $ampm );
96              
97             # Then we are able to check for most of the formats with this regexp
98             (
99 116 100 100     2368 ( $day, $mon, $yr, $hr, $min, $sec, $tz )
      100        
      100        
      100        
100             = /^
101             (\d\d?) # day
102             (?:\s+|[-\/])
103             (\w+) # month
104             (?:\s+|[-\/])
105             (\d+) # year
106             (?:
107             (?:\s+|:) # separator before clock
108             (\d\d?):(\d\d) # hour:min
109             (?::(\d\d))? # optional seconds
110             )? # optional clock
111             \s*
112             ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone
113             \s*
114             (?:\(\w+\)|\w{3,})? # ASCII representation of timezone.
115             \s*$
116             /x
117             )
118              
119             ||
120              
121             # Try the ctime and asctime format
122             (
123             ( $mon, $day, $hr, $min, $sec, $tz, $yr )
124             = /^
125             (\w{1,3}) # month
126             \s+
127             (\d\d?) # day
128             \s+
129             (\d\d?):(\d\d) # hour:min
130             (?::(\d\d))? # optional seconds
131             \s+
132             (?:([A-Za-z]+)\s+)? # optional timezone
133             (\d+) # year
134             \s*$ # allow trailing whitespace
135             /x
136             )
137              
138             ||
139              
140             # Then the Unix 'ls -l' date format
141             (
142             ( $mon, $day, $yr, $hr, $min, $sec )
143             = /^
144             (\w{3}) # month
145             \s+
146             (\d\d?) # day
147             \s+
148             (?:
149             (\d\d\d\d) | # year
150             (\d{1,2}):(\d{2}) # hour:min
151             (?::(\d\d))? # optional seconds
152             )
153             \s*$
154             /x
155             )
156              
157             ||
158              
159             # ISO 8601 format '1996-02-29 12:00:00 -0100' and variants
160             (
161             ( $yr, $mon, $day, $hr, $min, $sec, $tz )
162             = /^
163             (\d{4}) # year
164             [-\/]?
165             (\d\d?) # numerical month
166             [-\/]?
167             (\d\d?) # day
168             (?:
169             (?:\s+|[-:Tt]) # separator before clock
170             (\d\d?):?(\d\d) # hour:min
171             (?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional)
172             )? # optional clock
173             \s*
174             ([-+]?\d\d?:?(:?\d\d)?
175             |Z|z)? # timezone (Z is "zero meridian", i.e. GMT)
176             \s*$
177             /x
178             )
179              
180             ||
181              
182             # Windows 'dir' 11-12-96 03:52PM
183             (
184             ( $mon, $day, $yr, $hr, $min, $ampm )
185             = /^
186             (\d{2}) # numerical month
187             -
188             (\d{2}) # day
189             -
190             (\d{2}) # year
191             \s+
192             (\d\d?):(\d\d)([APap][Mm]) # hour:min AM or PM
193             \s*$
194             /x
195             )
196              
197             || return; # unrecognized format
198              
199             # Translate month name to number
200             $mon
201             = $MoY{$mon}
202 113   100     966 || $MoY{"\u\L$mon"}
203             || ( $mon =~ /^\d\d?$/ && $mon >= 1 && $mon <= 12 && int($mon) )
204             || return;
205              
206             # If the year is missing, we assume first date before the current,
207             # because of the formats we support such dates are mostly present
208             # on "ls -l" listings.
209 110 100 100     332 unless ( defined $yr ) {
210 1         3 my $cur_mon;
211 1         24 ( $cur_mon, $yr ) = (localtime)[ 4, 5 ];
212 1         5 $yr += 1900;
213 1         4 $cur_mon++;
214 1 50       5 $yr-- if $mon > $cur_mon;
215             }
216             elsif ( length($yr) < 3 ) {
217              
218             # Find "obvious" year
219             my $cur_yr = (localtime)[5] + 1900;
220             my $m = $cur_yr % 100;
221             my $tmp = $yr;
222             $yr += $cur_yr - $m;
223             $m -= $tmp;
224             $yr += ( $m > 0 ) ? 100 : -100
225             if abs($m) > 50;
226             }
227              
228             # Make sure clock elements are defined
229 110 100       223 $hr = 0 unless defined($hr);
230 110 100       302 $min = 0 unless defined($min);
231 110 100       198 $sec = 0 unless defined($sec);
232              
233             # Compensate for AM/PM
234 110 100       307 if ($ampm) {
235 11         20 $ampm = uc $ampm;
236 11 100 100     42 $hr = 0 if $hr == 12 && $ampm eq 'AM';
237 11 100 100     159 $hr += 12 if $ampm eq 'PM' && $hr != 12;
238             }
239              
240 110 100       476 return ( $yr, $mon, $day, $hr, $min, $sec, $tz )
241             if wantarray;
242              
243 2 50       8 if ( defined $tz ) {
244 0 0       0 $tz = "Z" if $tz =~ /^(GMT|UTC?|[-+]?0+)$/;
245             }
246             else {
247 2         5 $tz = "";
248             }
249 2         30 return sprintf(
250             "%04d-%02d-%02d %02d:%02d:%02d%s",
251             $yr, $mon, $day, $hr, $min, $sec, $tz
252             );
253             }
254              
255             sub time2iso (;$) {
256 11     11 1 109 my $time = shift;
257 11 100       25 $time = time unless defined $time;
258 11         498 my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time);
259 11         371 sprintf(
260             "%04d-%02d-%02d %02d:%02d:%02d",
261             $year + 1900, $mon + 1, $mday, $hour, $min, $sec
262             );
263             }
264              
265             sub time2isoz (;$) {
266 2     2 1 33 my $time = shift;
267 2 100       7 $time = time unless defined $time;
268 2         11 my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime($time);
269 2         62 sprintf(
270             "%04d-%02d-%02d %02d:%02d:%02dZ",
271             $year + 1900, $mon + 1, $mday, $hour, $min, $sec
272             );
273             }
274              
275             1;
276              
277             # ABSTRACT: HTTP::Date - date conversion routines
278             #
279              
280             __END__