File Coverage

blib/lib/HTML/CalendarMonth/DateTool.pm
Criterion Covered Total %
statement 560 634 88.3
branch 34 90 37.7
condition 8 51 15.6
subroutine 173 187 92.5
pod 9 9 100.0
total 784 971 80.7


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   38 use strict;
  10         9  
  10         247  
9 10     10   46 use warnings;
  10         9  
  10         200  
10 10     10   30 use Carp;
  10         8  
  10         466  
11              
12 10     10   3823 use File::Which qw( which );
  10         6315  
  10         16118  
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   1910 shift;
30 461         578 my $str = shift;
31 461         855 my $tool = $Toolmap{$str};
32 461 100       935 unless ($tool) {
33 308         771 foreach (values %Toolmap) {
34 1247 100       10685 if ($str =~ /^$_$/i) {
35 308         401 $tool = $_;
36 308         1376 last;
37             }
38             }
39             }
40 461 50       918 return unless $tool;
41 461         1352 join('::', __PACKAGE__, $tool);
42             }
43              
44             sub new {
45 310     310 1 1679 my $class = shift;
46 310         429 my $self = {};
47 310         536 bless $self, $class;
48 310         1133 my %parms = @_;
49 310         637 $self->{year} = $parms{year};
50 310         558 $self->{month} = $parms{month};
51 310         460 $self->{weeknum} = $parms{weeknum};
52 310         424 $self->{historic} = $parms{historic};
53 310 100       711 if (! $self->{year}) {
54 1         4 my @dmy = $self->_dmy_now;
55 1         3 $self->{year} = $dmy[2];
56 1   33     12 $self->{month} ||= $dmy[1];
57             }
58 310   50     807 $self->{month} ||= 1;
59 310 100       740 if ($parms{datetool}) {
60             $self->{datetool} = $self->_toolmap($parms{datetool})
61 152 50       488 or croak "Sorry, didn't find a tool for datetool '$parms{datetool}'\n";
62             }
63 310         988 my $dc = $self->_summon_date_class;
64 308 50       12780 unless (eval "require $dc") {
65 0         0 croak "Problem loading $dc ($@)\n";
66             }
67             # rebless into new class
68 308         1586 bless $self, $dc;
69             }
70              
71 1538     1538 1 3597 sub year { shift->{year} }
72 1228     1228 1 3200 sub month { shift->{month} }
73 310     310 1 1322 sub weeknum { shift->{weeknum} }
74 0     0 1 0 sub historic { shift->{historic} }
75 310     310 1 767 sub datetool { shift->{datetool} }
76              
77             sub _name {
78 446     446   657 my $class = shift;
79 446   66     1312 $class = ref $class || $class;
80 446         2044 lc((split(/::/, $class))[-1]);
81             }
82              
83             sub _cal_cmd {
84 1     1   360 my $self = shift;
85 1 50       4 if (! defined $Cal_Cmd) {
86 1   50     5 $Cal_Cmd = which('cal') || '';
87 1 50       239 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         2 $Cal_Cmd;
115             }
116              
117             sub _ncal_cmd {
118 1     1   366 my $self = shift;
119 1 50       3 if (! defined $Ncal_Cmd) {
120 1   50     4 $Ncal_Cmd = which('ncal') || '';
121 1 50       237 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         2 $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   402 my $self = shift;
167 307 50       877 @_ ? $self->{skips} = shift : $self->{skips};
168             }
169              
170 307     307 1 859 sub dow1st { (shift->dow1st_and_lastday)[0] }
171              
172 614     614 1 1731 sub lastday { (shift->dow1st_and_lastday)[1] }
173              
174             sub _dmy_now {
175 308     308   361 my $self = shift;
176 308 100       681 my $ts = @_ ? shift : time;
177 308         10587 my($d, $m, $y) = (localtime($ts))[3,4,5];
178 308         467 ++$m; $y += 1900;
  308         473  
179 308         863 ($d, $m, $y);
180             }
181              
182             sub _dom_now {
183 307     307   369 my $self = shift;
184 307 50       710 my $ts = @_ ? shift : time;
185 307         634 my($d, $m, $y);
186 307 50       1312 if ($ts =~ /^\d+$/) {
187 307 50       661 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         827 ($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         800 my($cy, $cm) = ($self->year, $self->month);
201 307         1509 my $first = sprintf("%04d/%02d/%02d", $cy, $cm, 1);
202 307         896 my $last = sprintf("%04d/%02d/%02d", $cy, $cm, $self->lastday);
203 307         737 my $pivot = sprintf("%04d/%02d/%02d", $y, $m, $d);
204 307 50       1147 return -1 if $pivot gt $last;
205 0 0       0 return 0 if $pivot lt $first;
206 0         0 $d;
207             }
208              
209             sub _summon_date_class {
210 310     310   394 my $self = shift;
211 310         352 my @tools;
212 310 100       895 if (my $c = $self->datetool) {
213 152     1   12058 eval "use $c";
  1     1   4  
  1     1   1  
  1     1   13  
  1     1   6  
  1     1   1  
  1     1   12  
  1     1   5  
  1     1   1  
  1     1   12  
  1     1   7  
  1     1   1  
  1     1   12  
  1     1   5  
  1     1   1  
  1     1   14  
  1     1   5  
  1     1   1  
  1     1   12  
  1     1   5  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   2  
  1     1   11  
  1     1   6  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   1  
  1     1   14  
  1     1   6  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   1  
  1     1   13  
  1     1   6  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   2  
  1     1   12  
  1     1   6  
  1     1   1  
  1     1   12  
  1     1   6  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   1  
  1     1   14  
  1     1   5  
  1     1   1  
  1     1   13  
  1     1   6  
  1     1   2  
  1     1   13  
  1     1   5  
  1     1   1  
  1     1   13  
  1     1   6  
  1     1   2  
  1     1   12  
  1     1   6  
  1     1   1  
  1     1   49  
  1     1   6  
  1     1   1  
  1     1   13  
  1     1   6  
  1     1   1  
  1     1   12  
  1     1   6  
  1     1   1  
  1     1   12  
  1     1   5  
  1     1   2  
  1     1   13  
  1     1   6  
  1     1   1  
  1     1   14  
  1     1   6  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   1  
  1     1   13  
  1     1   6  
  1     1   1  
  1     1   23  
  1     1   5  
  1     1   1  
  1     1   12  
  1     1   5  
  1     1   1  
  1     1   13  
  1     1   6  
  1     1   1  
  1     1   12  
  1     1   5  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   2  
  1     1   13  
  1     1   4  
  1     1   2  
  1     1   12  
  1     1   5  
  1     1   2  
  1     1   12  
  1     1   6  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   1  
  1     1   12  
  1     1   5  
  1     1   1  
  1     1   12  
  1     1   5  
  1     1   1  
  1     1   12  
  1     1   5  
  1     1   1  
  1     1   13  
  1     1   6  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   2  
  1     1   13  
  1     1   5  
  1     1   1  
  1     1   13  
  1     1   5  
  1     1   1  
  1     1   12  
  1     1   7  
  1     1   1  
  1     1   14  
  1     1   6  
  1     1   2  
  1     1   13  
  1     1   5  
  1     1   2  
  1     1   12  
  1     1   9  
  1     1   1  
  1     1   14  
  1     1   6  
  1         1  
  1         14  
  1         6  
  1         1  
  1         14  
  1         5  
  1         2  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         7  
  1         1  
  1         12  
  1         5  
  1         2  
  1         12  
  1         7  
  1         1  
  1         15  
  1         5  
  1         2  
  1         13  
  1         5  
  1         2  
  1         12  
  1         6  
  1         1  
  1         13  
  1         5  
  1         2  
  1         12  
  1         6  
  1         1  
  1         12  
  1         6  
  1         1  
  1         14  
  1         6  
  1         2  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         14  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         12  
  1         6  
  1         1  
  1         12  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         12  
  1         6  
  1         1  
  1         12  
  1         5  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         12  
  1         7  
  1         2  
  1         13  
  1         6  
  1         2  
  1         12  
  1         7  
  1         1  
  1         15  
  1         5  
  1         2  
  1         12  
  1         6  
  1         1  
  1         13  
  1         5  
  1         2  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         12  
  1         5  
  1         1  
  1         14  
  1         7  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         12  
  1         6  
  1         1  
  1         13  
  1         5  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         12  
  1         6  
  1         1  
  1         12  
  1         5  
  1         2  
  1         13  
  1         6  
  1         1  
  1         13  
  1         7  
  1         1  
  1         15  
  1         6  
  1         1  
  1         13  
  1         7  
  1         2  
  1         12  
  1         7  
  1         1  
  1         13  
  1         6  
  1         2  
  1         13  
  1         5  
  1         2  
  1         12  
  1         6  
  1         2  
  1         13  
  1         6  
  1         2  
  1         12  
  1         6  
  1         2  
  1         13  
  1         7  
  1         2  
  1         13  
  1         5  
  1         2  
  1         13  
  1         6  
  1         2  
  1         13  
  1         5  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         8  
  1         2  
  1         14  
  1         6  
  1         3  
  1         12  
  1         5  
  1         2  
  1         13  
  1         5  
  1         2  
  1         14  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         2  
  1         13  
  1         6  
  1         1  
  1         12  
  1         6  
  1         2  
  1         125  
  1         6  
  1         1  
  1         12  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         14  
  1         9  
  1         2  
  1         19  
  1         6  
  1         2  
  1         14  
  1         8  
  1         1  
  1         13  
  1         7  
  1         1  
  1         13  
  1         6  
  1         1  
  1         14  
  1         10  
  1         1  
  1         21  
  1         5  
  1         2  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         7  
  1         1  
  1         14  
  1         6  
  1         2  
  1         12  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         7  
  1         1  
  1         13  
  1         7  
  1         1  
  1         13  
  1         6  
  1         1  
  1         13  
  1         7  
  1         1  
  1         12  
  1         6  
  1         2  
  1         13  
  1         7  
  1         1  
  1         12  
  1         6  
  1         1  
  1         13  
  1         6  
  1         2  
  1         14  
  1         6  
  1         2  
  1         14  
  1         6  
  1         1  
  1         12  
  1         6  
  1         2  
  1         12  
  1         5  
  1         1  
  1         14  
  1         8  
  1         2  
  1         19  
214 152 50       449 die "invalid date tool $c : $@" if $@;
215 152         567 @tools = $c->_name;
216             }
217             else {
218 158         579 @tools = qw( timelocal datecalc datetime datemanip ncal cal );
219             }
220 310         400 my($dc, @fails);
221 310         537 for my $tool (@tools) {
222 310         830 my $method = join('_', '', lc($tool), 'fails');
223 310 100       1037 if (my $f = $self->$method) {
224 2         7 push(@fails, [$tool, $f]);
225             }
226             else {
227 308         806 $dc = $self->_toolmap($tool);
228 308         526 last;
229             }
230             }
231 310 100       967 return $dc if $dc;
232 2 50       5 if (@tools == 1) {
233 2         3 croak "invalid date tool " . join(': ', @{$fails[0]});
  2         366  
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   376 my $self = shift;
262 310 50       887 return "not installed" unless $self->_timelocal_present;
263 310 50       814 return "week-of-year numbering unsupported" if $self->weeknum;
264 310         782 my $y = $self->year;
265 310 100 66     1879 return "only years between 1970 and 2038 supported"
266             if $y < 1970 || $y >= 2038;
267 308         787 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   17033 sub _timelocal_present { eval "require Time::Local"; return !$@ }
  310         4934  
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__