File Coverage

blib/lib/HTML/CalendarMonth/DateTool.pm
Criterion Covered Total %
statement 562 634 88.6
branch 37 90 41.1
condition 8 51 15.6
subroutine 173 187 92.5
pod 9 9 100.0
total 789 971 81.2


line stmt bran cond sub pod time code
1             package HTML::CalendarMonth::DateTool;
2             {
3             $HTML::CalendarMonth::DateTool::VERSION = '1.26';
4             }
5              
6             # Base class for determining what date calculation package to use.
7              
8 10     10   49 use strict;
  10         20  
  10         261  
9 10     10   50 use warnings;
  10         19  
  10         260  
10 10     10   48 use Carp;
  10         22  
  10         621  
11              
12 10     10   7315 use File::Which qw( which );
  10         9481  
  10         24602  
13              
14             my %Toolmap = (
15             'Time::Local' => 'TimeLocal',
16             'Date::Calc' => 'DateCalc',
17             'DateTime' => 'DateTime',
18             'Date::Manip' => 'DateManip',
19             'ncal' => 'Ncal',
20             'cal' => 'Cal',
21             );
22              
23             my %Classmap;
24             $Classmap{lc $Toolmap{$_}} = $_ foreach keys %Toolmap;
25              
26             my($Cal_Cmd, $Ncal_Cmd);
27              
28             sub _toolmap {
29 461     461   3168 shift;
30 461         771 my $str = shift;
31 461         1163 my $tool = $Toolmap{$str};
32 461 100       1128 unless ($tool) {
33 308         1032 foreach (values %Toolmap) {
34 968 100       13406 if ($str =~ /^$_$/i) {
35 308         500 $tool = $_;
36 308         656 last;
37             }
38             }
39             }
40 461 50       1040 return unless $tool;
41 461         1754 join('::', __PACKAGE__, $tool);
42             }
43              
44             sub new {
45 310     310 1 2304 my $class = shift;
46 310         641 my $self = {};
47 310         711 bless $self, $class;
48 310         1484 my %parms = @_;
49 310         981 $self->{year} = $parms{year};
50 310         765 $self->{month} = $parms{month};
51 310         613 $self->{weeknum} = $parms{weeknum};
52 310         594 $self->{historic} = $parms{historic};
53 310 100       847 if (! $self->{year}) {
54 1         4 my @dmy = $self->_dmy_now;
55 1         2 $self->{year} = $dmy[2];
56 1   33     9 $self->{month} ||= $dmy[1];
57             }
58 310   50     939 $self->{month} ||= 1;
59 310 100       924 if ($parms{datetool}) {
60             $self->{datetool} = $self->_toolmap($parms{datetool})
61 152 50       639 or croak "Sorry, didn't find a tool for datetool '$parms{datetool}'\n";
62             }
63 310         1113 my $dc = $self->_summon_date_class;
64 308 50       15996 unless (eval "require $dc") {
65 0         0 croak "Problem loading $dc ($@)\n";
66             }
67             # rebless into new class
68 308         1929 bless $self, $dc;
69             }
70              
71 1538     1538 1 5705 sub year { shift->{year} }
72 1228     1228 1 4748 sub month { shift->{month} }
73 310     310 1 1567 sub weeknum { shift->{weeknum} }
74 0     0 1 0 sub historic { shift->{historic} }
75 310     310 1 1081 sub datetool { shift->{datetool} }
76              
77             sub _name {
78 446     446   830 my $class = shift;
79 446   66     1694 $class = ref $class || $class;
80 446         2616 lc((split(/::/, $class))[-1]);
81             }
82              
83             sub _cal_cmd {
84 1     1   570 my $self = shift;
85 1 50       4 if (! defined $Cal_Cmd) {
86 1   50     6 $Cal_Cmd = which('cal') || '';
87 1 50       315 if ($Cal_Cmd) {
88 0         0 my @out = grep { ! /^\s*$/ } `$Cal_Cmd 9 1752`;
  0         0  
89             # September 1752
90             #Su Mo Tu We Th Fr Sa
91             # 1 2 14 15 16
92             #17 18 19 20 21 22 23
93             #24 25 26 27 28 29 30
94 0         0 my @pat = (
95             qr/^\s*\S+\s+\d+$/,
96             qr/^\s*\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s*$/,
97             qr/^\s*\d+\s+\d+\s+\d+\s+\d+\s+\d+\s*$/,
98             qr/^\s*\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s*$/,
99             qr/^\s*\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s*$/,
100             );
101 0 0       0 if (@out == @pat) {
102 0         0 for my $i (0 .. $#out) {
103 0 0       0 if ($out[$i] !~ $pat[$i]) {
104 0         0 $Cal_Cmd = '';
105 0         0 last;
106             }
107             }
108             }
109             else {
110 0         0 $Cal_Cmd = '';
111             }
112             }
113             }
114 1         4 $Cal_Cmd;
115             }
116              
117             sub _ncal_cmd {
118 1     1   504 my $self = shift;
119 1 50       5 if (! defined $Ncal_Cmd) {
120 1   50     5 $Ncal_Cmd = which('ncal') || '';
121 1 50       271 if ($Ncal_Cmd) {
122 0         0 my @out = grep { ! /^\s*$/ } map { s/^\s*//; $_ } `$Ncal_Cmd 9 1752`;
  0         0  
  0         0  
  0         0  
123             # September 1752
124             #Mo 18 25
125             #Tu 1 19 26
126             #We 2 20 27
127             #Th 14 21 28
128             #Fr 15 22 29
129             #Sa 16 23 30
130             #Su 17 24
131 0         0 my @pat = (
132             qr/^\s*\S+\s+\d+$/,
133             qr/^\s*\S+\s+\d+\s+\d+\s*$/,
134             qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
135             qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
136             qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
137             qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
138             qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
139             qr/^\s*\S+\s+\d+\s+\d+\s*$/,
140             );
141 0 0       0 if (@out == @pat) {
142 0         0 for my $i (0 .. $#out) {
143 0 0       0 if ($out[$i] !~ $pat[$i]) {
144 0         0 $Ncal_Cmd = '';
145 0         0 last;
146             }
147             }
148             }
149             else {
150 0         0 $Ncal_Cmd = '';
151             }
152             }
153             }
154 1         4 $Ncal_Cmd;
155             }
156              
157             sub day_epoch {
158             # in case our subclasses are lazy
159 0     0 1 0 my($self, $day, $month, $year) = @_;
160 0   0     0 $month ||= $self->month;
161 0   0     0 $year ||= $self->year;
162 0         0 Time::Local::timegm(0,0,0,1,$month,$year);
163             }
164              
165             sub _skips {
166 307     307   482 my $self = shift;
167 307 50       1205 @_ ? $self->{skips} = shift : $self->{skips};
168             }
169              
170 307     307 1 1023 sub dow1st { (shift->dow1st_and_lastday)[0] }
171              
172 614     614 1 2259 sub lastday { (shift->dow1st_and_lastday)[1] }
173              
174             sub _dmy_now {
175 308     308   488 my $self = shift;
176 308 100       861 my $ts = @_ ? shift : time;
177 308         14273 my($d, $m, $y) = (localtime($ts))[3,4,5];
178 308         719 ++$m; $y += 1900;
  308         632  
179 308         1087 ($d, $m, $y);
180             }
181              
182             sub _dom_now {
183 307     307   572 my $self = shift;
184 307 50       919 my $ts = @_ ? shift : time;
185 307         468 my($d, $m, $y);
186 307 50       1542 if ($ts =~ /^\d+$/) {
187 307 50       845 if (length $ts <= 2) {
188 0         0 ($d, $m, $y) = ($ts, $self->month, $self->year);
189 0 0 0     0 croak "invalid day of month (1 .. " . $self->lastday . ") '$ts'"
190             unless $ts >= 1 && $ts <= $self->lastday;
191             }
192             else {
193 307         1080 ($d, $m, $y) = $self->_dmy_now($ts);
194             }
195             }
196             else {
197 0         0 ($y, $m, $d) = $ts =~ m{^(\d+)/(\d\d)/(\d\d)$};
198 0 0       0 croak "invalid yyyy/mm/dd date string '$ts'" unless defined $d;
199             }
200 307         1051 my($cy, $cm) = ($self->year, $self->month);
201 307         1653 my $first = sprintf("%04d/%02d/%02d", $cy, $cm, 1);
202 307         1124 my $last = sprintf("%04d/%02d/%02d", $cy, $cm, $self->lastday);
203 307         860 my $pivot = sprintf("%04d/%02d/%02d", $y, $m, $d);
204 307 100       1523 return -1 if $pivot gt $last;
205 8 100       53 return 0 if $pivot lt $first;
206 4         16 $d;
207             }
208              
209             sub _summon_date_class {
210 310     310   496 my $self = shift;
211 310         428 my @tools;
212 310 100       1080 if (my $c = $self->datetool) {
213 152     1   16067 eval "use $c";
  1     1   6  
  1     1   2  
  1     1   16  
  1     1   8  
  1     1   2  
  1     1   15  
  1     1   7  
  1     1   2  
  1     1   15  
  1     1   9  
  1     1   2  
  1     1   16  
  1     1   9  
  1     1   1  
  1     1   16  
  1     1   8  
  1     1   1  
  1     1   16  
  1     1   10  
  1     1   1  
  1     1   17  
  1     1   9  
  1     1   2  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   18  
  1     1   8  
  1     1   1  
  1     1   18  
  1     1   8  
  1     1   2  
  1     1   17  
  1     1   7  
  1     1   2  
  1     1   17  
  1     1   9  
  1     1   2  
  1     1   18  
  1     1   7  
  1     1   2  
  1     1   18  
  1     1   9  
  1     1   2  
  1     1   20  
  1     1   10  
  1     1   2  
  1     1   19  
  1     1   8  
  1     1   2  
  1     1   19  
  1     1   8  
  1     1   2  
  1     1   16  
  1     1   7  
  1     1   2  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   15  
  1     1   7  
  1     1   2  
  1     1   17  
  1     1   8  
  1     1   1  
  1     1   16  
  1     1   37  
  1     1   2  
  1     1   18  
  1     1   9  
  1     1   2  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   25  
  1     1   9  
  1     1   1  
  1     1   20  
  1     1   8  
  1     1   3  
  1     1   39  
  1     1   9  
  1     1   2  
  1     1   18  
  1     1   8  
  1     1   1  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   18  
  1     1   8  
  1     1   2  
  1     1   18  
  1     1   8  
  1     1   1  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   16  
  1     1   7  
  1     1   2  
  1     1   17  
  1     1   7  
  1     1   2  
  1     1   16  
  1     1   18  
  1     1   2  
  1     1   18  
  1     1   8  
  1     1   3  
  1     1   16  
  1     1   8  
  1     1   1  
  1     1   17  
  1     1   9  
  1     1   1  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   18  
  1     1   9  
  1     1   2  
  1     1   18  
  1     1   8  
  1     1   2  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   18  
  1     1   8  
  1     1   2  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   17  
  1     1   29  
  1     1   3  
  1     1   17  
  1     1   8  
  1     1   2  
  1     1   17  
  1     1   8  
  1         1  
  1         17  
  1         8  
  1         2  
  1         18  
  1         8  
  1         2  
  1         17  
  1         8  
  1         3  
  1         17  
  1         7  
  1         2  
  1         17  
  1         9  
  1         2  
  1         16  
  1         8  
  1         2  
  1         17  
  1         8  
  1         2  
  1         18  
  1         14  
  1         2  
  1         17  
  1         7  
  1         2  
  1         17  
  1         8  
  1         2  
  1         17  
  1         8  
  1         3  
  1         16  
  1         8  
  1         1  
  1         18  
  1         7  
  1         2  
  1         18  
  1         9  
  1         1  
  1         17  
  1         9  
  1         1  
  1         18  
  1         8  
  1         2  
  1         17  
  1         8  
  1         3  
  1         42  
  1         9  
  1         2  
  1         18  
  1         9  
  1         3  
  1         17  
  1         8  
  1         3  
  1         17  
  1         8  
  1         2  
  1         17  
  1         8  
  1         2  
  1         15  
  1         9  
  1         2  
  1         17  
  1         9  
  1         2  
  1         18  
  1         10  
  1         6  
  1         21  
  1         9  
  1         2  
  1         16  
  1         9  
  1         1  
  1         18  
  1         9  
  1         1  
  1         17  
  1         9  
  1         1  
  1         17  
  1         8  
  1         2  
  1         17  
  1         14  
  1         2  
  1         17  
  1         8  
  1         2  
  1         17  
  1         8  
  1         2  
  1         19  
  1         9  
  1         2  
  1         17  
  1         16  
  1         1  
  1         18  
  1         7  
  1         2  
  1         18  
  1         8  
  1         2  
  1         18  
  1         7  
  1         2  
  1         18  
  1         7  
  1         2  
  1         17  
  1         8  
  1         2  
  1         17  
  1         7  
  1         2  
  1         17  
  1         7  
  1         2  
  1         23  
  1         7  
  1         2  
  1         16  
  1         7  
  1         3  
  1         15  
  1         8  
  1         2  
  1         16  
  1         8  
  1         1  
  1         17  
  1         7  
  1         2  
  1         17  
  1         8  
  1         2  
  1         16  
  1         7  
  1         2  
  1         17  
  1         8  
  1         2  
  1         17  
  1         7  
  1         2  
  1         17  
  1         8  
  1         2  
  1         18  
  1         7  
  1         2  
  1         17  
  1         8  
  1         2  
  1         17  
  1         7  
  1         2  
  1         17  
  1         8  
  1         2  
  1         18  
  1         7  
  1         2  
  1         16  
  1         7  
  1         2  
  1         16  
  1         8  
  1         1  
  1         16  
  1         9  
  1         1  
  1         17  
  1         8  
  1         2  
  1         17  
  1         8  
  1         1  
  1         17  
  1         7  
  1         2  
  1         15  
  1         16  
  1         2  
  1         18  
  1         7  
  1         2  
  1         17  
  1         8  
  1         2  
  1         17  
  1         8  
  1         2  
  1         16  
  1         8  
  1         2  
  1         17  
  1         8  
  1         2  
  1         25  
  1         7  
  1         160  
  1         18  
  1         7  
  1         2  
  1         17  
  1         8  
  1         1  
  1         17  
  1         8  
  1         2  
  1         17  
  1         8  
  1         1  
  1         16  
  1         7  
  1         3  
  1         16  
  1         8  
  1         1  
  1         17  
  1         7  
  1         2  
  1         15  
  1         8  
  1         1  
  1         16  
  1         8  
  1         2  
  1         18  
  1         7  
  1         2  
  1         16  
  1         7  
  1         2  
  1         17  
  1         9  
  1         1  
  1         17  
  1         7  
  1         2  
  1         17  
  1         9  
  1         2  
  1         17  
  1         9  
  1         1  
  1         18  
  1         9  
  1         2  
  1         17  
  1         10  
  1         2  
  1         22  
  1         8  
  1         1  
  1         17  
  1         10  
  1         2  
  1         20  
  1         10  
  1         2  
  1         22  
  1         10  
  1         3  
  1         21  
  1         9  
  1         1  
  1         17  
  1         9  
  1         2  
  1         17  
  1         10  
  1         2  
  1         21  
  1         11  
  1         2  
  1         18  
  1         10  
  1         1  
  1         20  
  1         9  
  1         2  
  1         25  
  1         7  
  1         2  
  1         17  
  1         8  
  1         2  
  1         18  
  1         8  
  1         2  
  1         18  
  1         7  
  1         2  
  1         16  
214 152 50       581 die "invalid date tool $c : $@" if $@;
215 152         681 @tools = $c->_name;
216             }
217             else {
218 158         640 @tools = qw( timelocal datecalc datetime datemanip ncal cal );
219             }
220 310         620 my($dc, @fails);
221 310         744 for my $tool (@tools) {
222 310         929 my $method = join('_', '', lc($tool), 'fails');
223 310 100       1180 if (my $f = $self->$method) {
224 2         8 push(@fails, [$tool, $f]);
225             }
226             else {
227 308         903 $dc = $self->_toolmap($tool);
228 308         677 last;
229             }
230             }
231 310 100       1298 return $dc if $dc;
232 2 50       8 if (@tools == 1) {
233 2         4 croak "invalid date tool " . join(': ', @{$fails[0]});
  2         569  
234             }
235             else {
236 0         0 croak join("\n",
237             "no valid date tool found:",
238             map(sprintf("%11s: %s", @$_), @fails),
239             "\n"
240             );
241             }
242             }
243              
244             sub _dump_tests {
245 0     0   0 my $self = shift;
246 0   0     0 print "Time::Local : ", $self->_timelocal_fails || 1, "\n";
247 0   0     0 print " Date::Calc : ", $self->_datecalc_fails || 1, "\n";
248 0   0     0 print " DateTime : ", $self->_datetime_fails || 1, "\n";
249 0   0     0 print "Date::Manip : ", $self->_datemanip_fails || 1, "\n";
250 0   0     0 print " ncal : ", $self->_ncal_fails || 1, "\n";
251 0   0     0 print " cal : ", $self->_cal_fails || 1, "\n";
252             }
253              
254             sub _is_julian {
255 0     0   0 my $self = shift;
256 0         0 my $y = $self->year;
257 0 0 0     0 $y < 1752 || ($y == 1752 && $self->month <= 9);
258             }
259              
260             sub _timelocal_fails {
261 310     310   496 my $self = shift;
262 310 50       972 return "not installed" unless $self->_timelocal_present;
263 310 50       1020 return "week-of-year numbering unsupported" if $self->weeknum;
264 310         879 my $y = $self->year;
265 310 100 66     2220 return "only years between 1970 and 2038 supported"
266             if $y < 1970 || $y >= 2038;
267 308         1088 return;
268             }
269              
270             sub _ncal_fails {
271 0     0   0 my $self = shift;
272 0 0       0 return "command not found" unless $self->_ncal_present;
273 0 0 0     0 return "week-of-year numbering not supported prior to 1752/09"
274             if $self->weeknum && $self->_is_julian;
275 0         0 return;
276             }
277              
278             sub _cal_fails {
279 0     0   0 my $self = shift;
280 0 0       0 return "command not found" unless $self->_cal_present;
281 0 0       0 return "week-of-year numbering not supported" if $self->weeknum;
282 0         0 return;
283             }
284              
285             sub _datecalc_fails {
286 0     0   0 my $self = shift;
287 0 0       0 return "not installed" unless $self->_datecalc_present;
288 0 0 0     0 return "historic mode prior to 1752/09 not supported"
289             if $self->historic && $self->_is_julian;
290 0         0 return;
291             }
292              
293             sub _datetime_fails {
294 0     0   0 my $self = shift;
295 0 0       0 return "not installed" unless $self->_datetime_present;
296 0 0 0     0 return "historic mode prior to 1752/09 not supported"
297             if $self->historic && $self->_is_julian;
298 0         0 return;
299             }
300              
301             sub _datemanip_fails {
302 0     0   0 my $self = shift;
303 0 0       0 return "not installed" unless $self->_datemanip_present;
304 0 0 0     0 return "historic mode prior to 1752/09 not supported"
305             if $self->historic && $self->_is_julian;
306 0 0       0 eval { require Date::Manip && Date::Manip::Date_Init() };
  0         0  
307 0 0       0 return "init failure: $@" if $@;
308 0         0 return;
309             }
310              
311 310     310   21006 sub _timelocal_present { eval "require Time::Local"; return !$@ }
  310         6361  
312 0     0   0 sub _datecalc_present { eval "require Date::Calc"; return !$@ }
  0         0  
313 0     0   0 sub _datetime_present { eval "require DateTime"; return !$@ }
  0         0  
314 0     0   0 sub _datemanip_present { eval "require Date::Manip"; return !$@ }
  0         0  
315 0     0   0 sub _ncal_present { shift->_ncal_cmd }
316 0     0   0 sub _cal_present { shift->_cal_cmd };
317              
318              
319             1;
320              
321             __END__