File Coverage

blib/lib/Date/Parse.pm
Criterion Covered Total %
statement 156 199 78.3
branch 140 224 62.5
condition 72 129 55.8
subroutine 10 11 90.9
pod 1 2 50.0
total 379 565 67.0


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   1803282 use strict;
  16         43  
  16         640  
9 16     16   8281 use Time::Local;
  16         41638  
  16         1260  
10 16     16   121 use Carp;
  16         30  
  16         1095  
11 16     16   7627 use Time::Zone;
  16         55  
  16         1366  
12 16     16   99 use Exporter;
  16         23  
  16         14952  
13              
14             our @ISA = qw(Exporter);
15             our @EXPORT = qw(&strtotime &str2time &strptime);
16              
17             our $VERSION = '2.35'; # 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 126 local($day_ref,$mon_ref,$suf_ref,$obj) = @_;
234              
235 37 100       135 if($obj)
236             {
237 21         40 my $obj_strptime = $strptime;
238 21         289 substr($obj_strptime,index($strptime,"sub")+6,0) = <<'ESQ';
239             shift; # package
240             ESQ
241 21 50 66 9   45151 my $sub = eval "$obj_strptime" or die $@;
  9 50 66     20  
  9 50 0     24  
  9 50 0     13  
  9 50 0     17  
  9 50 33     29  
  9 50 0     41  
  9 50 33     76  
  9 100 33     45  
  9 50 66     55  
  9 50 66     484  
  9 50 33     47  
  9 0 33     62  
  0 0 33     0  
  0 0       0  
  9 0       48  
  0 50       0  
  9 50       24  
  9 50       93  
  1 100       8  
  1 50       5  
  1 0       4  
  0 0       0  
  0 50       0  
  9 50       58  
  0 50       0  
  9 0       24  
  9 0       1386  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 0       0  
  8 0       42  
  1 0       7  
  1 0       5  
  0 0       0  
  1 50       3  
  0 0       0  
  0 50       0  
  0 50       0  
  9 100       89  
  9 50       26  
  9 50       34  
  0 50       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  9         84  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  9         37  
  1         5  
  0         0  
  0         0  
  9         47  
  9         28  
  9         14  
  9         27  
  9         21  
  9         63  
  9         47  
242 21         96 return $sub;
243             }
244              
245 16 50 100 544   44150 eval "$strptime" or die $@;
  545 100 100     1619229  
  545 100 66     855  
  545 100 66     1082  
  545 100 66     1497  
  545 100 100     1894  
  545 100 33     6350  
  545 100 100     4706  
  545 100 66     4762  
  545 50 66     5260  
  545 100 66     2103  
  542 100 66     4028  
  4 100 100     51  
  79 100 66     645  
  541 50       2095  
  4 50       28  
  541 100       1166  
  464 100       3528  
  439 100       1933  
  439 100       1147  
  439 100       1115  
  1 100       63  
  0 50       0  
  541 100       3724  
  3 100       19  
  541 50       1126  
  457 50       9602  
  8 100       380  
  7 100       28  
  14 50       92  
  14 50       59  
  14 100       28  
  14 100       57  
  14 0       68  
  239 0       1023  
  173 0       539  
  174 100       456  
  3 50       7  
  171 100       291  
  7 100       34  
  0 100       0  
  0 100       0  
  456 100       3067  
  542 100       1663  
  542 100       3349  
  325 50       873  
  324 100       1090  
  324 100       1059  
  139         499  
  139         424  
  139         289  
  139         262  
  139         367  
  540         1481  
  20         122  
  16         33  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  19         55  
  537         1046  
  517         1381  
  31         101  
  10         17  
  537         2275  
  356         821  
  356         552  
  538         1593  
  538         1106  
  538         1291  
  535         2821  
246              
247             }
248              
249             *strptime = gen_parser(\%day,\%month,\@suf);
250              
251             sub str2time
252             {
253 526 100   525 1 3490996 my $now = @_ > 2 ? splice(@_, 2, 1) : time;
254 526         16894 my @t = strptime(@_);
255              
256             return undef
257 526 100       1292 unless @t;
258              
259 517         1642 my($ss,$mm,$hh,$day,$month,$year,$zone, $century) = @t;
260 517         10752 my @lt = localtime($now);
261              
262 517   100     1428 $hh ||= 0;
263 517   66     1023 $mm ||= 0;
264 517   66     1038 $ss ||= 0;
265              
266 517         955 my $frac = $ss - int($ss);
267 517         771 $ss = int $ss;
268              
269 517 100       1078 $month = $lt[4]
270             unless(defined $month);
271              
272 517 100       994 $day = $lt[3]
273             unless(defined $day);
274              
275 517 100       922 unless (defined $year) {
276 21   100     116 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       1036 $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       1173 $year += ($year >= 69 ? 1900 : 2000) if $year < 100;
    100          
290              
291             return undef
292 517 100 33     3969 unless($month <= 11 && $day >= 1 && $day <= 31
      33        
      33        
      33        
      66        
293             && $hh <= 23 && $mm <= 59 && $ss <= 59);
294              
295 517         689 my $result;
296              
297 517 100       866 if (defined $zone) {
298 471         875 $result = eval {
299 471     1   3939 local $SIG{__DIE__} = sub {}; # Ick!
300 470         1650 timegm($ss,$mm,$hh,$day,$month,$year);
301             };
302             return undef
303 470 100 33     19270 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     1058 return undef if $result < 0 && $year >= 1970;
309 469         658 $result -= $zone;
310             }
311             else {
312 47         87 $result = eval {
313 47     0   759 local $SIG{__DIE__} = sub {}; # Ick!
314 46         196 timelocal($ss,$mm,$hh,$day,$month,$year);
315             };
316             return undef
317 46 50 33     3502 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 100     133 return undef if $result < 0 && $year >= 1971;
324             }
325              
326 515         2096 return $result + $frac;
327             }
328              
329             1;
330              
331             __END__