File Coverage

lib/CGI/ValidOp/Check/date.pm
Criterion Covered Total %
statement 89 89 100.0
branch 68 70 97.1
condition 47 48 97.9
subroutine 17 17 100.0
pod 3 11 27.2
total 224 235 95.3


line stmt bran cond sub pod time code
1             package CGI::ValidOp::Check::date;
2 1     1   1735 use strict;
  1         2  
  1         41  
3 1     1   5 use warnings;
  1         2  
  1         28  
4              
5 1     1   5 use base qw/ CGI::ValidOp::Check /;
  1         1  
  1         438  
6              
7             my %TIMES = (
8             past => 1,
9             present => 1,
10             future => 1,
11             );
12              
13             sub iso {
14 78     78 1 191 my $self = shift;
15             sub {
16 78     78   236 my $value = shift;
17 78 100       189 my $times = [ grep { $TIMES{ $_ } if defined $_ } @_ ];
  78         1136  
18              
19 78 100       279 return $self->pass unless defined $value;
20              
21 77         1658 my $errmsg = '$label must include year, month, and date as YYYY-MM-DD';
22              
23 77 50       290 my ($y, $m, $d) = check_iso_format($value)
24             or return $self->fail( $errmsg );
25            
26 77 100 66     571 if ( $times and $times->[0] ) {
27 27         124 my ( $valid, $time ) = valid_date( $y, $m, $d, $times );
28 27 100       177 return $self->fail( '$label cannot be in the ' . $time )
29             unless ( $valid );
30             }
31              
32 59 100 100     202 if ( check_year($y) &&
      100        
33             check_month($m) &&
34             check_day($d, $m, $y) ) {
35              
36 33         329 return $self->pass( sprintf( "%02d-%02d-%02d", $y, $m, $d ));
37             }
38              
39 26         153 return $self->fail( $errmsg );
40             }
41 78         774 }
42              
43             sub american {
44 16     16 1 27 my $self = shift;
45             sub {
46 16     16   38 my $value = shift;
47 16 100       61 return $self->pass unless defined $value;
48              
49 15         30 my $errmsg = '$label must be a valid date in a standard American format: mm/dd/yyyy or mm-dd-yyyy. (Leading zeros are not required)';
50              
51 15 50       41 my( $y, $m, $d ) = check_american_format($value)
52             or return $self->fail( $errmsg );
53              
54 15 100 100     43 if ( check_year($y) &&
      100        
55             check_month($m) &&
56             check_day($d, $m, $y) ) {
57              
58 6         48 return $self->pass( sprintf( '%d-%02d-%02d', $y, $m, $d ));
59             }
60              
61 9         34 return $self->fail( $errmsg );
62             }
63 16         109 }
64              
65             sub general {
66 21     21 1 35 my $self = shift;
67             sub {
68 21     21   51 my $value = shift;
69 21 100       72 return $self->pass unless defined $value;
70              
71 20         38 my $errmsg = '$label must be a valid date in one of the following formats: mm/dd/yyyy, mm-dd-yyyy, yyyy-mm-dd. (Leading zeros are not required)';
72              
73 20         58 my( $y, $m, $d ) =
74             check_american_format($value);
75 20 100       82 unless (defined $y) {
76 11         37 ($y, $m, $d) = check_iso_format($value);
77             }
78              
79 20 100 100     59 if ( check_year($y) &&
      100        
80             check_month($m) &&
81             check_day($d, $m, $y) ) {
82              
83 11         123 return $self->pass( sprintf( '%d-%02d-%02d', $y, $m, $d ));
84             }
85              
86 9         36 return $self->fail( $errmsg );
87             }
88              
89 21         145 }
90              
91             sub valid_date {
92 27     27 0 68 my ( $y, $m, $d, $times ) = @_;
93 27         87 my @today = today();
94 27         87 my @value = ( $y, $m, $d );
95              
96 27         51 my $time = 'present';
97 27         90 for ( my $i = 0; $i < 3; $i++ ) {
98 63 100       192 if ( $today[$i] > $value[$i] ) {
99 9         15 $time = 'past';
100 9         16 last;
101             }
102 54 100       189 if ( $today[$i] < $value[$i] ) {
103 9         21 $time = 'future';
104 9         17 last;
105             }
106             }
107 27 100       120 return (grep { m/$time/ } @$times) ? 1 : 0, $time;
  27         298  
108             }
109              
110             sub today {
111 28     28 0 1598 my ($sec,$min,$hour,$mday,$mon,$year) = localtime time;
112 28         149 return ( $year + 1900, $mon + 1, $mday);
113             }
114              
115             # Checks that given date is in iso format and returns array
116             # of year, month, day strings if so, else undef.
117             sub check_iso_format {
118 92     92 0 313 my $date = shift;
119 92 100       396 return unless defined $date;
120              
121 91 100       1581 my( $y, $m, $d ) =
122             $date =~ qr#^(\d{1,4})-(\d{1,2})-(\d{1,2})$#
123             or return undef;
124              
125 77         552 return ($y, $m, $d);
126             }
127              
128             # Checks that given date is in american format and returns
129             # array of year, month, day strings if so, else undef.
130             sub check_american_format {
131 39     39 0 87 my $date = shift;
132 39 100       136 return unless defined $date;
133              
134 38 100       522 my( $m, $d, $y ) =
135             $date =~ qr#^(\d{1,2})(?:-|/)(\d{1,2})(?:-|/)(\d{4})$#
136             or return undef;
137              
138 19         119 return ($y, $m, $d);
139             }
140              
141             # Returns 1 if year is a 4 digit number.
142             sub check_year {
143 98     98 0 220 my $y = shift;
144 98 100       327 return unless defined $y;
145 79 100       3532 return 1 if $y =~ qr/^\d{4}$/;
146 2         185 return 0;
147             }
148              
149             # Returns 1 if month is between 1 and 12. Accepts 01, 02...
150             sub check_month {
151 84     84 0 327 my $m = shift;
152 84 100       229 return unless defined $m;
153 83 100 100     1673 return 1 if $m =~ qr/^\d{1,2}$/ and $m > 0 and $m < 13;
      100        
154 8         49 return 0;
155             }
156              
157             # Requires day and month; requires year if month is February.
158             # Returns 1 if day is valid for month/year. 0 if not.
159             # Returns undefined if insufficient parameters given.
160             sub check_day {
161 104     104 0 265 my( $d, $m, $y ) = @_;
162 104 100 100     737 return unless defined $d and defined $m;
163             # checking February's day requires the year for leap years
164 102 100 100     841 return unless $m != 2 or defined $y;
165              
166 101 100 100     1519 return 0 if $d !~ qr/^\d{1,2}$/ or $d < 1 or $d >31;
      100        
167              
168             # 30 days hath september, april, june and november
169 83 100 100     941 if ($m == 4 || $m == 6 || $m == 9 || $m == 11 ) {
    100 100        
    100 100        
170 26 100       159 return 1 if $d <= 30;
171             }
172             # all the rest have 31
173             elsif ($m != 2) {
174 27         145 return 1;
175             }
176             # except February, which has 28
177             elsif ( not leap_year($y)) {
178 15 100       80 return 1 if $d <= 28;
179             }
180             # or on a leap year, 29
181             else {
182 15 100       96 return 1 if $d <= 29;
183             }
184 20         103 return 0;
185             }
186              
187             sub leap_year {
188 30     30 0 55 my $y = shift;
189 30 100       153 return 0 if $y % 4; # not multiple of 4
190 17 100       63 return 1 unless $y % 400; # is multiple of 400
191 9 100       40 return 0 unless $y % 100; # is multiple of 100
192 7         37 return 1; # everything else
193             }
194             1;
195              
196             __END__