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   71774 use strict;
  1         13  
  1         1834  
4              
5             our $VERSION = '6.06';
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 1869 my $time = shift;
23 1 50       6 $time = time unless defined $time;
24 1         10 my ( $sec, $min, $hour, $mday, $mon, $year, $wday ) = gmtime($time);
25 1         13 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 121     121 1 76207 my $str = shift;
35 121 100       253 return undef unless defined $str;
36              
37             # fast exit for strictly conforming string
38 120 100       251 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         7 my $t = Time::Local::timegm( $6, $5, $4, $1, $MoY{$2} - 1, $3 );
43 1 50       52 $t < 0 ? undef : $t;
44             };
45             }
46              
47 119         215 my @d = parse_date($str);
48 119 100       248 return undef unless @d;
49 113         158 $d[1]--; # month
50              
51 113         171 my $tz = pop(@d);
52 113 100       193 unless ( defined $tz ) {
53 57 100       120 unless ( defined( $tz = shift ) ) {
54 18         29 return eval {
55 18         24 my $frac = $d[-1];
56 18         33 $frac -= ( $d[-1] = int($frac) );
57 18         53 my $t = Time::Local::timelocal( reverse @d ) + $frac;
58 13 50       1021 $t < 0 ? undef : $t;
59             };
60             }
61             }
62              
63 95         150 my $offset = 0;
64 95 100       286 if ( $GMT_ZONE{ uc $tz } ) {
    50          
65              
66             # offset already zero
67             }
68             elsif ( $tz =~ /^([-+])?(\d\d?):?(\d\d)?$/ ) {
69 27         73 $offset = 3600 * $2;
70 27 50       66 $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 95         124 return eval {
80 95         135 my $frac = $d[-1];
81 95         161 $frac -= ( $d[-1] = int($frac) );
82 95         246 my $t = Time::Local::timegm( reverse @d ) + $frac;
83 95 50       3271 $t < 0 ? undef : $t - $offset;
84             };
85             }
86              
87             sub parse_date ($) {
88 122     122 1 3404 local ($_) = shift;
89 122 50       208 return unless defined;
90              
91             # More lax parsing below
92 122         334 s/^\s+//; # kill leading space
93 122         288 s/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*//i; # Useless weekday
94              
95 122         235 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 122 100 100     1730 ( $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' and four-digit year variant
183             (
184             ( $mon, $day, $yr, $hr, $min, $ampm )
185             = /^
186             (\d{2}) # numerical month
187             -
188             (\d{2}) # day
189             -
190             (\d{2,4}) # 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 119   100     914 || $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 116 100 100     352 unless ( defined $yr ) {
210 1         5 my $cur_mon;
211 1         27 ( $cur_mon, $yr ) = (localtime)[ 4, 5 ];
212 1         5 $yr += 1900;
213 1         3 $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 116 100       219 $hr = 0 unless defined($hr);
230 116 100       208 $min = 0 unless defined($min);
231 116 100       184 $sec = 0 unless defined($sec);
232              
233             # Compensate for AM/PM
234 116 100       221 if ($ampm) {
235 17         34 $ampm = uc $ampm;
236 17 100 100     63 $hr = 0 if $hr == 12 && $ampm eq 'AM';
237 17 100 100     45 $hr += 12 if $ampm eq 'PM' && $hr != 12;
238             }
239              
240 116 100       497 return ( $yr, $mon, $day, $hr, $min, $sec, $tz )
241             if wantarray;
242              
243 2 50       7 if ( defined $tz ) {
244 0 0       0 $tz = "Z" if $tz =~ /^(GMT|UTC?|[-+]?0+)$/;
245             }
246             else {
247 2         5 $tz = "";
248             }
249 2         22 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 45 my $time = shift;
257 14 100       33 $time = time unless defined $time;
258 14         233 my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time);
259 14         130 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 310 my $time = shift;
267 2 100       7 $time = time unless defined $time;
268 2         10 my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime($time);
269 2         12 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__