File Coverage

blib/lib/Date/Parse.pm
Criterion Covered Total %
statement 156 199 78.3
branch 140 224 62.5
condition 69 129 53.4
subroutine 10 11 90.9
pod 1 2 50.0
total 376 565 66.5


line stmt bran cond sub pod time code
1             # Copyright (c) 1995-2009 Graham Barr. This program is free
2             # software; you can redistribute it and/or modify it under the same terms
3             # as Perl itself.
4              
5             package Date::Parse;
6              
7             require 5.000;
8 16     16   1579525 use strict;
  16         33  
  16         652  
9 16     16   7164 use Time::Local;
  16         37778  
  16         1173  
10 16     16   109 use Carp;
  16         27  
  16         953  
11 16     16   6725 use Time::Zone;
  16         60  
  16         1609  
12 16     16   99 use Exporter;
  16         31  
  16         16292  
13              
14             our @ISA = qw(Exporter);
15             our @EXPORT = qw(&strtotime &str2time &strptime);
16              
17             our $VERSION = '2.34_03'; # TRIAL VERSION: generated
18             # ABSTRACT: Parse date strings into time values
19              
20             my %month = (
21             january => 0,
22             february => 1,
23             march => 2,
24             april => 3,
25             may => 4,
26             june => 5,
27             july => 6,
28             august => 7,
29             september => 8,
30             sept => 8,
31             october => 9,
32             november => 10,
33             december => 11,
34             );
35              
36             my %day = (
37             sunday => 0,
38             monday => 1,
39             tuesday => 2,
40             tues => 2,
41             wednesday => 3,
42             wednes => 3,
43             thursday => 4,
44             thur => 4,
45             thurs => 4,
46             friday => 5,
47             saturday => 6,
48             );
49              
50             my @suf = (qw(th st nd rd th th th th th th)) x 3;
51             @suf[11,12,13] = qw(th th th);
52              
53             #Abbreviations
54              
55             map { $month{substr($_,0,3)} = $month{$_} } keys %month;
56             map { $day{substr($_,0,3)} = $day{$_} } keys %day;
57              
58             my $strptime = <<'ESQ';
59             my %month = map { lc $_ } %$mon_ref;
60             my $daypat = join("|", map { lc $_ } reverse sort keys %$day_ref);
61             my $monpat = join("|", reverse sort keys %month);
62             my $sufpat = join("|", reverse sort map { lc $_ } @$suf_ref);
63              
64             my %ampm = (
65             'a' => 0, # AM
66             'p' => 12, # PM
67             );
68              
69             my($AM, $PM) = (0,12);
70              
71             sub {
72              
73             my $dtstr = lc shift;
74             my $merid = 24;
75              
76             my($century,$year,$month,$day,$hh,$mm,$ss,$zone,$dst,$frac);
77              
78             $zone = tz_offset(shift) if @_;
79              
80             1 while $dtstr =~ s#\([^\(\)]*\)# #o;
81              
82             $dtstr =~ s#(\A|\n|\Z)# #sog;
83              
84             # ignore day names
85             $dtstr =~ s#([\d\w\s])[\.\,]\s#$1 #sog;
86             $dtstr =~ s/(?
87             $dtstr =~ s#($daypat)\s*(den\s)?\b# #o;
88             # Time: 12:00 or 12:00:00 with optional am/pm
89              
90             return unless $dtstr =~ /\S/;
91              
92             # ISO compact: YYYYMMDD without delimiter (month and day must be exactly 2 digits)
93             if ($dtstr =~ s/\s(\d{4})(\d\d)(\d\d)(?:[-Tt ](\d\d?)(?:([-:]?)(\d\d?)(?:\5(\d\d?)(?:[.,](\d+))?)?)?)?(?=\D)/ /) {
94             ($year,$month,$day,$hh,$mm,$ss,$frac) = ($1,$2-1,$3,$4,$6,$7,$8);
95             }
96             # Date with explicit delimiter: YYYY[-:]MM[-:]DD
97             elsif ($dtstr =~ s/\s(\d{4})([-:])(\d\d?)\2(\d\d?)(?:[-Tt ](\d\d?)(?:([-:]?)(\d\d?)(?:\6(\d\d?)(?:[.,](\d+))?)?)?)?(?=\D)/ /) {
98             ($year,$month,$day,$hh,$mm,$ss,$frac) = ($1,$3-1,$4,$5,$7,$8,$9);
99             }
100              
101             # default C++ boost timestamp is effectively %Y-%b-%d %H:%M:%S.%f
102             # details: https://svn.boost.org/trac/boost/ticket/8839
103             if ($dtstr =~ s/\s(\d{4})([-:]?)(\w{3,})\2(\d\d?)(?:[-Tt ](\d\d?)(?:([-:]?)(\d\d?)(?:\6(\d\d?)(?:[.,](\d+))?)?)?)?(?=\D)/ /) {
104             ($year,$month,$day,$hh,$mm,$ss,$frac) = ($1,$month{$3},$4,$5,$7,$8,$9);
105             }
106              
107             unless (defined $hh) {
108             if ($dtstr =~ s#[:\s](\d\d?):(\d\d?)(:(\d\d?)(?:\.\d+)?)?(z)?\s*(?:([ap])\.?m?\.?)?\s# #o) {
109             ($hh,$mm,$ss) = ($1,$2,$4);
110             $zone = 0 if $5;
111             $merid = $ampm{$6} if $6;
112             }
113              
114             # Time: 12 am
115              
116             elsif ($dtstr =~ s#\s(\d\d?)\s*([ap])\.?m?\.?\s# #o) {
117             ($hh,$mm,$ss) = ($1,0,0);
118             $merid = $ampm{$2};
119             }
120             }
121              
122             if (defined $hh and $hh <= 12 and $dtstr =~ s# ([ap])\.?m?\.?\s# #o) {
123             $merid = $ampm{$1};
124             }
125              
126              
127             unless (defined $year) {
128             # Date: 12-June-96 (using - . or /)
129              
130             if ($dtstr =~ s#\s(\d\d?)([\-\./])($monpat)(\2(\d\d+))?\s# #o) {
131             ($month,$day) = ($month{$3},$1);
132             $year = $5 if $5;
133             }
134              
135             # Date: 12-12-96 (using '-', '.' or '/' )
136              
137             elsif ($dtstr =~ s#\s(\d+)([\-\./])(\d\d?)(\2(\d+))?\s# #o) {
138             ($month,$day) = ($1 - 1,$3);
139              
140             if ($5) {
141             $year = $5;
142             # Possible match for 1995-01-24 (short mainframe date format);
143             ($year,$month,$day) = ($1, $3 - 1, $5) if $month > 12;
144             return if length($year) > 2 and $year < 1901;
145             }
146             }
147             elsif ($dtstr =~ s#\s(\d+)\s*($sufpat)?\s*($monpat)# #o) {
148             ($month,$day) = ($month{$3},$1);
149             }
150             elsif ($dtstr =~ s#($monpat)\s*(\d+)\s*($sufpat)?\s# #o) {
151             $month = $month{$1};
152             if ($2 > 31) { $year = $2 } else { $day = $2 }
153             }
154             elsif ($dtstr =~ s#($monpat)([\/-])(\d+)[\/-]# #o) {
155             ($month,$day) = ($month{$1},$3);
156             }
157              
158             # Date: 961212 (YYMMDD — only consume if month is in range 1-12)
159              
160             elsif ($dtstr =~ /\s(\d\d)(\d\d)(\d\d)\s/o && $2 >= 1 && $2 <= 12) {
161             $dtstr =~ s/\s(\d\d)(\d\d)(\d\d)\s/ /;
162             ($year,$month,$day) = ($1,$2-1,$3);
163             }
164              
165             $year = $1 if !defined($year) and $dtstr =~ s#\s(\d{2}(\d{2})?)[\s\.,]# #o;
166              
167             }
168              
169             # Zone
170              
171             $dst = 1 if $dtstr =~ s#\bdst\b##o;
172              
173             if ($dtstr =~ s#\s"?([a-z]{3,4})(dst|\d+[a-z]*|_[a-z]+)?"?\s# #o) {
174             $dst = 1 if $2 and $2 eq 'dst';
175             $zone = tz_offset($1);
176             return unless defined $zone;
177             }
178             elsif ($dtstr =~ s#\s([a-z]{3,4})?([\-\+]?)-?(\d\d?):?(\d\d)?(00)?\s# #o) {
179             my $m = defined($4) ? "$2$4" : 0;
180             my $h = "$2$3";
181             $zone = defined($1) ? tz_offset($1) : 0;
182             return unless defined $zone;
183             $zone += 60 * ($m + (60 * $h));
184             }
185              
186             if ($dtstr =~ /\S/) {
187             # now for some dumb dates
188             if ($dtstr =~ s/^\s*(ut?|z)\s*$//) {
189             $zone = 0;
190             }
191             elsif ($dtstr =~ s#\s([a-z]{3,4})?([\-\+]?)-?(\d\d?)(\d\d)?(00)?\s# #o) {
192             my $m = defined($4) ? "$2$4" : 0;
193             my $h = "$2$3";
194             $zone = defined($1) ? tz_offset($1) : 0;
195             return unless defined $zone;
196             $zone += 60 * ($m + (60 * $h));
197             }
198              
199             return if $dtstr =~ /\S/o;
200             }
201              
202             if (defined $hh) {
203             if ($hh == 12) {
204             $hh = 0 if $merid == $AM;
205             }
206             elsif ($merid == $PM) {
207             $hh += 12;
208             }
209             }
210              
211             if (defined $year && $year >= 100) {
212             $century = int($year / 100);
213             $year -= 1900;
214             }
215              
216             $zone += 3600 if defined $zone && $dst;
217             $ss += "0.$frac" if $frac;
218              
219             # Reject inputs that produced only a timezone with no date/time components.
220             # A bare number like '1' or '+0500' gets consumed by the timezone regex,
221             # leaving no meaningful date or time information — these are not valid dates.
222             return unless defined $hh || defined $mm || defined $ss
223             || defined $day || defined $month || defined $year;
224              
225             return ($ss,$mm,$hh,$day,$month,$year,$zone,$century);
226             }
227             ESQ
228              
229             our ($day_ref, $mon_ref, $suf_ref, $obj);
230              
231             sub gen_parser
232             {
233 37     37 0 121 local($day_ref,$mon_ref,$suf_ref,$obj) = @_;
234              
235 37 100       146 if($obj)
236             {
237 21         57 my $obj_strptime = $strptime;
238 21         341 substr($obj_strptime,index($strptime,"sub")+6,0) = <<'ESQ';
239             shift; # package
240             ESQ
241 21 50 66 9   64974 my $sub = eval "$obj_strptime" or die $@;
  9 50 33     22  
  9 50 0     25  
  9 50 0     17  
  9 50 0     16  
  9 50 33     32  
  9 50 0     31  
  9 50 33     77  
  9 100 33     46  
  9 50 66     55  
  9 50 66     576  
  9 50 33     50  
  9 0 33     75  
  0 0 33     0  
  0 0       0  
  9 0       30  
  0 50       0  
  9 50       22  
  9 50       64  
  1 100       8  
  1 50       4  
  1 0       3  
  0 0       0  
  0 50       0  
  9 50       32  
  0 50       0  
  9 0       21  
  9 0       1482  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 0       0  
  8 0       66  
  1 0       9  
  1 0       5  
  0 0       0  
  1 50       3  
  0 0       0  
  0 50       0  
  0 50       0  
  9 100       128  
  9 50       60  
  9 50       35  
  0 50       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  9         24  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  9         22  
  1         4  
  0         0  
  0         0  
  9         45  
  9         25  
  9         16  
  9         25  
  9         21  
  9         65  
  9         50  
242 21         148 return $sub;
243             }
244              
245 16 50 100 544   46330 eval "$strptime" or die $@;
  545 100 100     1759003  
  545 100 66     970  
  545 100 66     1010  
  545 100 66     1559  
  545 100 100     1915  
  545 100 33     6106  
  545 100 100     4929  
  545 100 66     4863  
  545 50 66     5132  
  545 100 66     2332  
  542 100 66     4134  
  4 100 100     40  
  79 100 66     650  
  541 50       2174  
  4 50       38  
  541 100       1257  
  464 100       3423  
  439 100       1900  
  439 100       1357  
  439 100       1110  
  1 100       4  
  0 50       0  
  541 100       4081  
  3 100       38  
  541 50       1241  
  457 50       10497  
  8 100       376  
  7 100       26  
  14 50       104  
  14 50       57  
  14 100       34  
  14 100       62  
  14 0       79  
  239 0       1166  
  173 0       461  
  174 100       470  
  3 50       11  
  171 100       265  
  7 100       38  
  0 100       0  
  0 100       0  
  456 100       3606  
  542 100       1536  
  542 100       3625  
  325 50       956  
  324 100       1042  
  324 100       982  
  139         556  
  139         419  
  139         322  
  139         325  
  139         412  
  540         1644  
  20         120  
  16         29  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  19         60  
  537         1119  
  517         1443  
  31         91  
  10         24  
  537         2086  
  356         841  
  356         557  
  538         1721  
  538         1051  
  538         1428  
  535         2958  
246              
247             }
248              
249             *strptime = gen_parser(\%day,\%month,\@suf);
250              
251             sub str2time
252             {
253 526 100   525 1 2903564 my $now = @_ > 2 ? splice(@_, 2, 1) : time;
254 526         16999 my @t = strptime(@_);
255              
256             return undef
257 526 100       1234 unless @t;
258              
259 517         1719 my($ss,$mm,$hh,$day,$month,$year,$zone, $century) = @t;
260 517         9271 my @lt = localtime($now);
261              
262 517   66     1382 $hh ||= 0;
263 517   66     1084 $mm ||= 0;
264 517   66     1106 $ss ||= 0;
265              
266 517         1041 my $frac = $ss - int($ss);
267 517         729 $ss = int $ss;
268              
269 517 100       1395 $month = $lt[4]
270             unless(defined $month);
271              
272 517 100       953 $day = $lt[3]
273             unless(defined $day);
274              
275 517 100       1054 unless (defined $year) {
276 21   100     110 my $is_future = $month > $lt[4]
277             || ($month == $lt[4] && $day > $lt[3]);
278 21 100       71 $year = $is_future ? ($lt[5] - 1) : $lt[5];
279             }
280              
281             # we were given a 4 digit year, so let's keep using those
282 517 100       1019 $year += 1900 if defined $century;
283              
284             # Normalize two-digit years to 4-digit before passing to Time::Local.
285             # Time::Local's own windowing varies across versions, so we do it ourselves.
286             # Convention: 69-99 -> 1969-1999, 0-68 -> 2000-2068 (POSIX strptime behavior).
287             # Note: first-century dates (years 1-99 AD) are not representable through
288             # str2time — same limitation as POSIX strptime.
289 516 100       1273 $year += ($year >= 69 ? 1900 : 2000) if $year < 100;
    100          
290              
291             return undef
292 517 100 33     4300 unless($month <= 11 && $day >= 1 && $day <= 31
      33        
      33        
      33        
      66        
293             && $hh <= 23 && $mm <= 59 && $ss <= 59);
294              
295 517         841 my $result;
296              
297 517 100       979 if (defined $zone) {
298 471         769 $result = eval {
299 471     1   4095 local $SIG{__DIE__} = sub {}; # Ick!
300 470         1845 timegm($ss,$mm,$hh,$day,$month,$year);
301             };
302             return undef
303 470 100 33     19520 if !defined $result
      66        
304             or $result == -1
305             && join("",$ss,$mm,$hh,$day,$month,$year)
306             ne "595923311169";
307             # Detect integer overflow: post-1970 dates must produce a non-negative epoch
308 470 50 66     1166 return undef if $result < 0 && $year >= 1970;
309 469         668 $result -= $zone;
310             }
311             else {
312 47         97 $result = eval {
313 47     0   5059 local $SIG{__DIE__} = sub {}; # Ick!
314 46         241 timelocal($ss,$mm,$hh,$day,$month,$year);
315             };
316             return undef
317 46 50 33     4114 if !defined $result
      33        
318             or $result == -1
319             && join("",$ss,$mm,$hh,$day,$month,$year)
320             ne join("",(localtime(-1))[0..5]);
321             # Detect integer overflow: post-1970 dates must produce a non-negative epoch
322             # Use 1971 to avoid false positives from timezone offsets near epoch 0
323 46 50 66     153 return undef if $result < 0 && $year >= 1971;
324             }
325              
326 515         2273 return $result + $frac;
327             }
328              
329             1;
330              
331             __END__