File Coverage

blib/lib/Calendar/List.pm
Criterion Covered Total %
statement 120 120 100.0
branch 70 70 100.0
condition 21 23 91.3
subroutine 12 12 100.0
pod 2 2 100.0
total 225 227 99.1


line stmt bran cond sub pod time code
1             package Calendar::List;
2              
3 13     13   1841456 use strict;
  13         80  
  13         393  
4 13     13   75 use warnings;
  13         24  
  13         411  
5              
6 13     13   64 use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT_OK @EXPORT);
  13         24  
  13         1953  
7             $VERSION = '1.01';
8              
9             #----------------------------------------------------------------------------
10              
11             =head1 NAME
12              
13             Calendar::List - A module for creating date lists
14              
15             =head1 SYNOPSIS
16              
17             use Calendar::List;
18              
19             # basic usage
20             my %hash = calendar_list('DD-MM-YYYY' => 'DD MONTH, YYYY' );
21             my @list = calendar_list('MM-DD-YYYY');
22             my $html = calendar_selectbox('DD-MM-YYYY' => 'DAY DDEXT MONTH, YYYY');
23              
24             # using the hash
25             my %hash01 = (
26             'options' => 10,
27             'exclude' => { 'weekend' => 1 },
28             'start' => '01-05-2003',
29             );
30              
31             my %hash02 = (
32             'options' => 10,
33             'exclude' => { 'holidays' => \@holidays },
34             'start' => '01-05-2003',
35             );
36              
37             my %hash03 = (
38             'exclude' => { 'monday' => 1,
39             'tuesday' => 1,
40             'wednesday' => 1 },
41             'start' => '01-05-2003',
42             'end' => '10-05-2003',
43             'name' => 'MyDates',
44             'selected' => '04-05-2003',
45             );
46              
47             my %hash = calendar_list('DD-MM-YYYY' => 'DDEXT MONTH YYYY', \%hash01);
48             my @list = calendar_list('DD-MM-YYYY', \%hash02);
49             my $html = calendar_selectbox('DD-MM-YYYY',\%hash03);
50              
51             =head1 DESCRIPTION
52              
53             The module is intended to be used to return a simple list, hash or scalar
54             of calendar dates. This is achieved by two functions, calendar_list and
55             calendar_selectbox. The former allows a return of a list of dates and a
56             hash of dates, whereas the later returns a scalar containing a HTML code
57             snippet for use as a HTML Form field select box.
58              
59             =head1 EXPORT
60              
61             calendar_list,
62             calendar_selectbox
63              
64             =cut
65              
66             #----------------------------------------------------------------------------
67              
68             #############################################################################
69             #Export Settings #
70             #############################################################################
71              
72             require Exporter;
73              
74             @ISA = qw(Exporter);
75              
76             %EXPORT_TAGS = ( 'all' => [ qw(
77             calendar_list
78             calendar_selectbox
79             ) ] );
80              
81             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
82             @EXPORT = ( @{ $EXPORT_TAGS{'all'} } );
83              
84             #############################################################################
85             #Library Modules #
86             #############################################################################
87              
88 13     13   5548 use Calendar::Functions qw(:all);
  13         46  
  13         2629  
89 13     13   5732 use Clone qw(clone);
  13         31951  
  13         721  
90 13     13   6587 use Tie::IxHash;
  13         32397  
  13         20511  
91              
92             #############################################################################
93             #Variables
94             #############################################################################
95              
96             # prime our print out names
97             my @months = qw( NULL January February March April May June July
98             August September October November December );
99             my @dotw = qw( Sunday Monday Tuesday Wednesday Thursday Friday Saturday );
100              
101             my (%months,%dotw);
102             for my $key (1..12) { $months{lc $months[$key]} = $key }
103             for my $key (0..6) { $dotw{ lc $dotw[$key] } = $key }
104              
105             # THE DEFAULTS
106             my $Format = 'DD-MM-YYYY';
107             my @order = qw( day month year );
108              
109             my %Defaults = (
110             maxcount => 30,
111             selectname => 'calendar',
112             selected => [],
113             startdate => undef,
114             enddate => undef,
115             start => [1,1,1970],
116             end => [31,12,2037],
117             holidays => {},
118             exclude => {
119             days => [ 0,0,0,0,0,0,0 ],
120             months => [ 0,0,0,0,0,0,0,0,0,0,0,0,0 ],
121             },
122             );
123              
124             my (%Settings);
125              
126             #----------------------------------------------------------------------------
127              
128             #############################################################################
129             #Interface Functions #
130             #############################################################################
131              
132             =head1 FUNCTIONS
133              
134             =over 4
135              
136             =item calendar_list([DATEFORMAT] [,DATEFORMAT] [,OPTIONSHASH])
137              
138             Returns a list in an array context or a hash reference in any other context.
139             All paramters are optional, one or two date formats can be specified for the
140             date formats returned in the list/hash. A hash of user defined settings can
141             also be passed into the function. See below for further details.
142              
143             Note that a second date format is not required when returning a list. A
144             single date format when returning a hash reference, will be used in both
145             key and value portions.
146              
147             =cut
148              
149             sub calendar_list {
150 50 100 100 50 1 37725 my $wantarray = (@_ < 2 || ref($_[1]) eq 'HASH') ? 1 : 0;
151 50         140 my ($fmt1,$fmt2,$hash) = _thelist(@_);
152 50         176 return _callist($fmt1,$fmt2,$hash,$wantarray);
153             }
154              
155             =item calendar_selectbox([DATEFORMAT] [,DATEFORMAT] [,OPTIONSHASH])
156              
157             Returns a scalar containing a HTML string. The HTML snippet consists of an
158             HTML form field select box. All paramters are optional, one or two date
159             formats can be specified for the date formats returned in the value
160             attribute and data portion. A hash of user defined settings can
161             also be passed into the function. See below for further details.
162              
163             Note that a single date format will be used in both value attribute and
164             data portions.
165              
166             =cut
167              
168             sub calendar_selectbox {
169 45     45 1 31075 my ($fmt1,$fmt2,$hash) = _thelist(@_);
170 45         118 return _calselect($fmt1,$fmt2,$hash);
171             }
172              
173             #############################################################################
174             #Internal Functions #
175             #############################################################################
176              
177             # name: _thelist
178             # args: format string 1 .... optional
179             # format string 2 .... optional
180             # settings hash ...... optional
181             # retv: undef if invalid settings, otherwise a hash of dates, keyed by
182             # an incremental counter.
183             # desc: The heart of the engine. Arranges the parameters passed to the
184             # the interface function, calls for the settings to be decided,
185             # them creates the main hash table of dates.
186             # Stops when either the end date is reached, or the maximum number
187             # of entries have been found.
188              
189             sub _thelist {
190 96     96   598 my ($format1,$format2,$usrhash);
191 96 100       302 $format1 = shift unless(ref($_[0]) eq 'HASH');
192 96 100       242 $format2 = shift unless(ref($_[0]) eq 'HASH');
193 96 100       243 $usrhash = shift if(ref($_[0]) eq 'HASH');
194              
195 96 100       218 $format1 = $Format unless($format1);
196 96 100       203 $format2 = $format1 unless($format2);
197              
198 96 100       228 return if _setargs($usrhash,$format1);
199              
200 95         213 $Settings{nowdate} = $Settings{startdate};
201              
202 95         146 my $optcount = 0; # our option counter
203 95         139 my %DateHash;
204 95         471 tie(%DateHash, 'Tie::IxHash');
205              
206 95         1582 while($optcount < $Settings{maxcount}) {
207 1595         3708 my ($nowday,$nowmon,$nowyear,$nowdow) = decode_date($Settings{nowdate});
208              
209             # ignore days we're not interested in
210 1595 100 100     6359 unless( $Settings{exclude}{days}->[$nowdow]
211             || $Settings{exclude}{months}->[$nowmon]) {
212              
213             # store the date, unless its a holiday
214 1000         3611 my $fdate = sprintf "%02d-%02d-%04d", $nowday,$nowmon,$nowyear;
215             $DateHash{$optcount++} = [decode_date($Settings{nowdate})]
216 1000 100       2965 unless($Settings{holidays}->{$fdate});
217             }
218              
219             # stop if reached end date
220 1595 100       18091 last if(compare_dates($Settings{nowdate},$Settings{enddate}) == 0);
221              
222             # increment
223 1548         3368 $Settings{nowdate} = add_day($Settings{nowdate});
224             }
225              
226 95         398 return $format1,$format2,\%DateHash;
227             }
228              
229             # name: _callist
230             # args: format string 1 .... optional
231             # format string 2 .... optional
232             # settings hash ...... optional
233             # retv: undef if invalid settings, otherwise an array if zero or one
234             # date format provided, in ascending order, or a hash if two
235             # date formats.
236             # desc: The cream on top. Takes the hash provided by _thelist and uses
237             # it to create a formatted array or hash.
238              
239             sub _callist {
240 51     51   752 my ($fmt1,$fmt2,$hash,$wantarray) = @_;
241 51 100       121 return unless($hash);
242              
243 50         76 my (@returns,%returns);
244 50         220 tie(%returns, 'Tie::IxHash');
245              
246 50         897 foreach my $key (sort {$a <=> $b} keys %$hash) {
  665         3328  
247 508         3379 my $date1 = format_date($fmt1,@{$hash->{$key}});
  508         1682  
248 508 100       1217 if($wantarray) {
249 307         648 push @returns, $date1;
250             } else {
251 201         266 my $date2 = format_date($fmt2,@{$hash->{$key}});
  201         668  
252 201         808 $returns{$date1} = $date2;
253             }
254             }
255              
256 50 100       832 return @returns if($wantarray);
257 21         70 return %returns;
258             }
259              
260              
261             # name: _calselect
262             # args: format string 1 .... optional
263             # format string 2 .... optional
264             # settings hash ...... optional
265             # retv: undef if invalid settings, otherwise a hash of dates, keyed by
266             # an incremental counter.
267             # desc: The cream on top. Takes the hash provided by _thelist and uses
268             # it to create a HTML select box form field, making use of any
269             # user defined settings.
270              
271             sub _calselect {
272 46     46   509 my ($fmt1,$fmt2,$hash) = @_;
273 46 100       109 return unless($hash);
274              
275             # open SELECT tag
276 45         118 my $select = "<select name='$Settings{selectname}'>\n";
277              
278             # add an OPTION elements
279 45         184 foreach my $key (sort {$a <=> $b} keys %$hash) {
  636         3206  
280 486         776 my $selected = 0;
281              
282             # check whether this option has been selected
283             $selected = 1
284 486         1260 if( @{$Settings{selected}} &&
285             $hash->{$key}->[0] == $Settings{selected}->[0] &&
286             $hash->{$key}->[1] == $Settings{selected}->[1] &&
287 486 100 100     653 $hash->{$key}->[2] == $Settings{selected}->[2]);
      100        
      66        
288              
289             # format date strings
290 486         1457 my $date1 = format_date($fmt1,@{$hash->{$key}});
  486         1593  
291 486         938 my $date2 = format_date($fmt2,@{$hash->{$key}});
  486         1600  
292              
293             # create the option
294 486         1278 $select .= "<option value='$date1'";
295 486 100       1133 $select .= ' selected="selected"' if($selected);
296 486         953 $select .= ">$date2</option>\n";
297             }
298              
299             # close SELECT tag
300 45         155 $select .= "</select>\n";
301 45         460 return $select;
302             }
303              
304             # name: _setargs
305             # args: settings hash ...... optional
306             # retv: 1 to indicate any bad settings, otherwise undef.
307             # desc: Sets defaults, then deciphers user defined settings.
308              
309             sub _setargs {
310 109     109   8007 my $hash = shift;
311 109         166 my $format1 = shift;
312              
313             # set the current date
314 109         3109 my @now = localtime();
315 109         490 my @today = ( $now[3], $now[4]+1, $now[5]+1900 );
316              
317 109         882 %Settings = ();
318 109         186 %Settings = %{ clone(\%Defaults) };
  109         4943  
319 109         637 $Settings{startdate} = encode_date(@today);
320              
321             # if no user hash table provided, lets go
322 109 100       335 return unless($hash);
323              
324 91         393 for my $key1 (keys %$hash) {
325              
326             # store excluded days
327 351 100       1374 if(lc $key1 eq 'exclude') {
    100          
    100          
    100          
    100          
    100          
328 66         113 for my $key2 (keys %{$hash->{$key1}}) {
  66         256  
329 147         277 my $inx = $dotw{lc $key2};
330              
331             # exclude days of the week
332 147 100 100     436 if(defined $inx) {
    100          
    100          
    100          
    100          
333 92         192 $Settings{exclude}{days}->[$inx] = $hash->{$key1}{$key2};
334              
335             # exclude months
336             } elsif($inx = $months{lc $key2}) {
337 19         53 $Settings{exclude}{months}->[$inx] = $hash->{$key1}{$key2};
338              
339             # exclude weekends
340             } elsif(lc $key2 eq 'weekend') {
341 16         50 $Settings{exclude}{days}->[0] = $hash->{$key1}{$key2};
342 16         51 $Settings{exclude}{days}->[6] = $hash->{$key1}{$key2};
343            
344             # exclude weekdays
345             } elsif(lc $key2 eq 'weekday') {
346 15         46 for my $index (1..5) { $Settings{exclude}{days}->[$index] = $hash->{$key1}{$key2}; }
  75         146  
347            
348             # check for holiday setting
349             } elsif(lc $key2 eq 'holidays' and ref($hash->{$key1}{$key2}) eq 'ARRAY') {
350 3         5 %{$Settings{holidays}} = map {$_ => 1} @{$hash->{$key1}{$key2}};
  3         14  
  9         17  
  3         8  
351             }
352             }
353              
354             # ensure we aren't wasting time
355 66         136 my $count = 0;
356 66 100       142 foreach my $inx (0..6) { $count++ if($Settings{exclude}{days}->[$inx]) }
  462         876  
357 66 100       166 return 1 if($count == 7);
358 65         112 $count = 0;
359 65 100       107 foreach my $inx (1..12) { $count++ if($Settings{exclude}{months}->[$inx]) }
  780         1369  
360 65 100       163 return 1 if($count == 12);
361              
362             # store selected date
363             } elsif(lc $key1 eq 'select') {
364 39         311 my @dates = ($hash->{$key1} =~ /(\d+)/g);
365 39         137 $Settings{selected} = \@dates;
366              
367             # store start date
368             } elsif(lc $key1 eq 'start') {
369 90         608 my @dates = ($hash->{$key1} =~ /(\d+)/g);
370 90         269 $Settings{startdate} = encode_date(@dates);
371              
372             # store end date
373             } elsif(lc $key1 eq 'end') {
374 75   50     221 $Settings{maxcount} ||= 9999;
375 75         514 my @dates = ($hash->{$key1} =~ /(\d+)/g);
376 75         219 $Settings{enddate} = encode_date(@dates);
377              
378             # store user defined values
379             } elsif(lc $key1 eq 'options') {
380 34         83 $Settings{maxcount} = $hash->{$key1};
381             } elsif(lc $key1 eq 'name') {
382 46         121 $Settings{selectname} = $hash->{$key1};
383             }
384             }
385              
386             # check whether we have a bad start/end dates
387 89 100       306 return 1 if(!$Settings{startdate});
388 88 100 100     449 return 1 if( $Settings{enddate} && compare_dates($Settings{enddate},$Settings{startdate}) < 0);
389 87 100       228 return 1 if(!$Settings{maxcount});
390              
391 85         368 return 0;
392             }
393              
394             1;
395              
396             __END__
397              
398             #----------------------------------------------------------------------------
399              
400             =back
401              
402             =head1 DATE FORMATS
403              
404             =over 4
405              
406             =item Parameters
407              
408             The date formatted parameters passed to the two exported functions can take
409             many different formats. If a single array is required then only one date
410             format string is required.
411              
412             Each format string can have the following components:
413              
414             DD
415             MM
416             YYYY
417             DAY
418             MONTH
419             DDEXT
420             DMY
421             MDY
422             YMD
423             MABV
424             DABV
425             EPOCH
426              
427             The first three are translated into the numerical day/month/year strings.
428             The DAY format is translated into the day of the week name, and MONTH
429             is the month name. DDEXT is the day with the appropriate suffix, eg 1st,
430             22nd or 13th. DMY, MDY and YMD default to '13-09-1965' (DMY) style strings.
431             MABV and DABV provide 3 letter abbreviations of MONTH and DAY respectively.
432              
433             EPOCH is translated into the number od seconds since the system epoch. Note
434             that the Time::Piece module must be installed to use this format.
435              
436             =item Options
437              
438             In the optional hash that can be passed to either function, it should be
439             noted that all 3 date formatted strings MUST be in the format 'DD-MM-YYYY'.
440              
441             =back
442              
443             =head1 OPTIONAL SETTINGS
444              
445             An optional hash of settings can be passed as the last parameter to each
446             external function, which consists of user defined limitations. Each
447             setting will effect the contents of the returned lists. This may lead to
448             conflicts, which will result in an undefined reference being returned.
449              
450             =over 4
451              
452             =item options
453              
454             The maximum number of items to be returned in the list.
455              
456             Note that where 'options' and 'end' are both specified, 'options' takes
457             precedence.
458              
459             =item name
460              
461             Used by calendar_selectbox. Names the select box form field.
462              
463             =item select
464              
465             Used by calendar_selectbox. Predefines the selected entry in a select box.
466              
467             =item exclude
468              
469             The exclude key allows the user to defined which days they wish to exclude
470             from the returned list. This can either consist of individual days or the
471             added flexibility of 'weekend' and 'weekday' to exclude a traditional
472             group of days. Full list is:
473              
474             weekday
475             monday
476             tuesday
477             wednesday
478             thursday
479             friday
480             weekend
481             saturday
482             sunday
483              
484             =item start
485              
486             References a start date in the format DD-MM-YYYY.
487              
488             =item end
489              
490             References an end date in the format DD-MM-YYYY. Note that if an end
491             date has been set alongside a setting for the maximum number of options,
492             the limit will be defined by which one is reached first.
493              
494             Note that where 'options' and 'end' are both specified, 'options' takes
495             precedence.
496              
497             =back
498              
499             =head1 DATE MODULES
500              
501             Internal to the Calendar::Functions module, there is some date comparison
502             code. As a consequence, this requires some date modules that can handle a
503             wide range of dates. There are three modules which are tested for you,
504             these are, in order of preference, Date::ICal, DateTime and Time::Local.
505              
506             Each module has the ability to handle dates, although only Time::Local exists
507             in the core release of Perl. Unfortunately Time::Local is limited by the
508             Operating System. On a 32bit machine this limit means dates before the epoch
509             (1st January, 1970) and after the rollover (January 2038) will not be
510             represented. If this date range is well within your scope, then you can safely
511             allow the module to use Time::Local. However, should you require a date range
512             that exceedes this range, then it is recommend that you install one of the two
513             other modules.
514              
515             =head1 SEE ALSO
516              
517             Calendar::Functions
518              
519             Clone
520             Date::ICal
521             DateTime
522             Time::Local
523             Time::Piece
524              
525             The Calendar FAQ at http://www.tondering.dk/claus/calendar.html
526              
527             =head1 BUGS, PATCHES & FIXES
528              
529             There are no known bugs at the time of this release. However, if you spot a
530             bug or are experiencing difficulties that are not explained within the POD
531             documentation, please submit a bug to the RT system (see link below). However,
532             it would help greatly if you are able to pinpoint problems or even supply a
533             patch.
534              
535             Fixes are dependent upon their severity and my availability. Should a fix not
536             be forthcoming, please feel free to (politely) remind me by sending an email
537             to barbie@cpan.org .
538              
539             RT: http://rt.cpan.org/Public/Dist/Display.html?Name=Calendar-List
540              
541             =head1 AUTHOR
542              
543             Barbie, <barbie@cpan.org>
544             for Miss Barbell Productions <http://www.missbarbell.co.uk>.
545              
546             =head1 THANKS TO
547              
548             Dave Cross, E<lt>dave at dave.orgE<gt> for creating Calendar::Simple, the
549             newbie poster on a technical message board who inspired me to write the
550             original code and Richard Clamp E<lt>richardc at unixbeard.co.ukE<gt>
551             for testing the beta versions.
552              
553             =head1 COPYRIGHT AND LICENSE
554              
555             Copyright (C) 2003-2019 Barbie for Miss Barbell Productions
556              
557             This distribution is free software; you can redistribute it and/or
558             modify it under the Artistic License v2.
559              
560             =cut