File Coverage

blib/lib/HTTP/Date.pm
Criterion Covered Total %
statement 70 71 98.5
branch 52 60 86.6
condition 25 26 96.1
subroutine 6 6 100.0
pod 5 5 100.0
total 158 168 94.0


line stmt bran cond sub pod time code
1             package HTTP::Date;
2              
3 4     4   494357 use strict;
  4         5  
  4         7825  
4              
5             our $VERSION = '6.07';
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 2087 my $time = shift;
23 1 50       6 $time = time unless defined $time;
24 1         5 my ( $sec, $min, $hour, $mday, $mon, $year, $wday ) = gmtime($time);
25 1         12 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 141     141 1 925455 my $str = shift;
35 141 100       288 return undef unless defined $str;
36              
37             # fast exit for strictly conforming string
38 140 100       254 if ( $str
39             =~ /^[SMTWF][a-z][a-z], ([0-9][0-9]) ([JFMAJSOND][a-z][a-z]) ([0-9][0-9][0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) GMT$/
40             ) {
41 3         6 return eval {
42 3         20 my $t = Time::Local::timegm( $6, $5, $4, $1, $MoY{$2} - 1, $3 );
43 3 100       133 $t < 0 ? undef : $t;
44             };
45             }
46              
47 137         214 my @d = parse_date($str);
48 137 100       223 return undef unless @d;
49 126         129 $d[1]--; # month
50              
51 126         137 my $tz = pop(@d);
52 126 100       190 unless ( defined $tz ) {
53 63 100       97 unless ( defined( $tz = shift ) ) {
54 20         24 return eval {
55 20         26 my $frac = $d[-1];
56 20         32 $frac -= ( $d[-1] = int($frac) );
57 20         49 my $t = Time::Local::timelocal( reverse @d ) + $frac;
58 15 100       913 $t < 0 ? undef : $t;
59             };
60             }
61             }
62              
63 106         126 my $offset = 0;
64 106 100       249 if ( $GMT_ZONE{ uc $tz } ) {
    100          
65              
66             # offset already zero
67             }
68             elsif ( $tz =~ /^([-+])?([0-9][0-9]?):?([0-9][0-9])?$/ ) {
69 29         52 $offset = 3600 * $2;
70 29 50       56 $offset += 60 * $3 if $3;
71 29 100 100     80 $offset *= -1 if $1 && $1 eq '-';
72             }
73             else {
74 4 50       5 eval { require Time::Zone } || return undef;
  4         583  
75 4         1354 $offset = Time::Zone::tz_offset($tz);
76 4 100       115 return undef unless defined $offset;
77             }
78              
79 105         107 return eval {
80 105         109 my $frac = $d[-1];
81 105         127 $frac -= ( $d[-1] = int($frac) );
82 105         203 my $t = Time::Local::timegm( reverse @d ) + $frac;
83 105 100       2532 $t < 0 ? undef : $t - $offset;
84             };
85             }
86              
87             sub parse_date ($) {
88 149     149 1 4673 local ($_) = shift;
89 149 50       222 return unless defined;
90              
91             # More lax parsing below
92 149         343 s/^\s+//; # kill leading space
93 149         287 s/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*//i; # Useless weekday
94              
95 149         180 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 149 100 100     2030 ( $day, $mon, $yr, $hr, $min, $sec, $tz )
      100        
      100        
      100        
100             = /^
101             ([0-9][0-9]?) # day
102             (?:\s+|[-\/])
103             (\w+) # month
104             (?:\s+|[-\/])
105             ([0-9]+) # year
106             (?:
107             (?:\s+|:) # separator before clock
108             ([0-9][0-9]?):([0-9][0-9]) # hour:min
109             (?::([0-9][0-9]))? # optional seconds
110             )? # optional clock
111             \s*
112             ([-+]?[0-9]{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             ([0-9][0-9]?) # day
128             \s+
129             ([0-9][0-9]?):([0-9][0-9]) # hour:min
130             (?::([0-9][0-9]))? # optional seconds
131             \s+
132             (?:([A-Za-z]+)\s+)? # optional timezone
133             ([0-9]+) # 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             ([0-9][0-9]?) # day
147             \s+
148             (?:
149             ([0-9][0-9][0-9][0-9]) | # year
150             ([0-9]{1,2}):([0-9]{2}) # hour:min
151             (?::([0-9][0-9]))? # 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             ([0-9]{4}) # year
164             [-\/]?
165             ([0-9][0-9]?) # numerical month
166             [-\/]?
167             ([0-9][0-9]?) # day
168             (?:
169             (?:\s+|[-:Tt]) # separator before clock
170             ([0-9][0-9]?):?([0-9][0-9]) # hour:min
171             (?::?([0-9][0-9](?:\.[0-9]*)?))? # optional seconds (and fractional)
172             )? # optional clock
173             \s*
174             ([-+]?[0-9][0-9]?:?(?:[0-9][0-9])?
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' and four-digit year variant
183             (
184             ( $mon, $day, $yr, $hr, $min, $ampm )
185             = /^
186             ([0-9]{2}) # numerical month
187             -
188             ([0-9]{2}) # day
189             -
190             ([0-9]{2,4}) # year
191             \s+
192             ([0-9][0-9]?):([0-9][0-9])([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 137   100     860 || $MoY{"\u\L$mon"}
203             || ( $mon =~ /^[0-9][0-9]?$/ && $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 133 100 66     282 unless ( defined $yr ) {
210 1         1 my $cur_mon;
211 1         9 ( $cur_mon, $yr ) = (localtime)[ 4, 5 ];
212 1         3 $yr += 1900;
213 1         1 $cur_mon++;
214 1 50       4 $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 133 100       175 $hr = 0 unless defined($hr);
230 133 100       167 $min = 0 unless defined($min);
231 133 100       174 $sec = 0 unless defined($sec);
232              
233             # Compensate for AM/PM
234 133 100       164 if ($ampm) {
235 17         22 $ampm = uc $ampm;
236 17 100 100     44 $hr = 0 if $hr == 12 && $ampm eq 'AM';
237 17 100 100     36 $hr += 12 if $ampm eq 'PM' && $hr != 12;
238             }
239              
240 133 100       448 return ( $yr, $mon, $day, $hr, $min, $sec, $tz )
241             if wantarray;
242              
243 2 50       5 if ( defined $tz ) {
244 0 0       0 $tz = "Z" if $tz =~ /^(GMT|UTC?|[-+]?0+)$/;
245             }
246             else {
247 2         4 $tz = "";
248             }
249 2         15 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 14     14 1 27 my $time = shift;
257 14 100       35 $time = time unless defined $time;
258 14         120 my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time);
259 14         84 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 382 my $time = shift;
267 2 100       5 $time = time unless defined $time;
268 2         7 my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime($time);
269 2         9 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__