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   1867731 use strict;
  13         63  
  13         383  
4 13     13   67 use warnings;
  13         26  
  13         451  
5              
6 13     13   70 use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT_OK @EXPORT);
  13         25  
  13         1869  
7             $VERSION = '1.00';
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   5447 use Calendar::Functions qw(:all);
  13         47  
  13         2685  
89 13     13   5804 use Clone qw(clone);
  13         32584  
  13         766  
90 13     13   6379 use Tie::IxHash;
  13         32367  
  13         20309  
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 39274 my $wantarray = (@_ < 2 || ref($_[1]) eq 'HASH') ? 1 : 0;
151 50         147 my ($fmt1,$fmt2,$hash) = _thelist(@_);
152 50         173 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 31333 my ($fmt1,$fmt2,$hash) = _thelist(@_);
170 45         111 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   600 my ($format1,$format2,$usrhash);
191 96 100       307 $format1 = shift unless(ref($_[0]) eq 'HASH');
192 96 100       249 $format2 = shift unless(ref($_[0]) eq 'HASH');
193 96 100       255 $usrhash = shift if(ref($_[0]) eq 'HASH');
194              
195 96 100       220 $format1 = $Format unless($format1);
196 96 100       202 $format2 = $format1 unless($format2);
197              
198 96 100       230 return if _setargs($usrhash,$format1);
199              
200 95         223 $Settings{nowdate} = $Settings{startdate};
201              
202 95         150 my $optcount = 0; # our option counter
203 95         142 my %DateHash;
204 95         472 tie(%DateHash, 'Tie::IxHash');
205              
206 95         1557 while($optcount < $Settings{maxcount}) {
207 1595         3690 my ($nowday,$nowmon,$nowyear,$nowdow) = decode_date($Settings{nowdate});
208              
209             # ignore days we're not interested in
210 1595 100 100     6337 unless( $Settings{exclude}{days}->[$nowdow]
211             || $Settings{exclude}{months}->[$nowmon]) {
212              
213             # store the date, unless its a holiday
214 1000         3713 my $fdate = sprintf "%02d-%02d-%04d", $nowday,$nowmon,$nowyear;
215             $DateHash{$optcount++} = [decode_date($Settings{nowdate})]
216 1000 100       3101 unless($Settings{holidays}->{$fdate});
217             }
218              
219             # stop if reached end date
220 1595 100       17895 last if(compare_dates($Settings{nowdate},$Settings{enddate}) == 0);
221              
222             # increment
223 1548         3492 $Settings{nowdate} = add_day($Settings{nowdate});
224             }
225              
226 95         369 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   740 my ($fmt1,$fmt2,$hash,$wantarray) = @_;
241 51 100       123 return unless($hash);
242              
243 50         93 my (@returns,%returns);
244 50         219 tie(%returns, 'Tie::IxHash');
245              
246 50         917 foreach my $key (sort {$a <=> $b} keys %$hash) {
  665         3925  
247 508         3464 my $date1 = format_date($fmt1,@{$hash->{$key}});
  508         1733  
248 508 100       1207 if($wantarray) {
249 307         663 push @returns, $date1;
250             } else {
251 201         284 my $date2 = format_date($fmt2,@{$hash->{$key}});
  201         725  
252 201         877 $returns{$date1} = $date2;
253             }
254             }
255              
256 50 100       878 return @returns if($wantarray);
257 21         75 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   540 my ($fmt1,$fmt2,$hash) = @_;
273 46 100       112 return unless($hash);
274              
275             # open SELECT tag
276 45         113 my $select = "<select name='$Settings{selectname}'>\n";
277              
278             # add an OPTION elements
279 45         182 foreach my $key (sort {$a <=> $b} keys %$hash) {
  636         2896  
280 486         775 my $selected = 0;
281              
282             # check whether this option has been selected
283             $selected = 1
284 486         1239 if( @{$Settings{selected}} &&
285             $hash->{$key}->[0] == $Settings{selected}->[0] &&
286             $hash->{$key}->[1] == $Settings{selected}->[1] &&
287 486 100 100     563 $hash->{$key}->[2] == $Settings{selected}->[2]);
      100        
      66        
288              
289             # format date strings
290 486         1349 my $date1 = format_date($fmt1,@{$hash->{$key}});
  486         1535  
291 486         901 my $date2 = format_date($fmt2,@{$hash->{$key}});
  486         1505  
292              
293             # create the option
294 486         1096 $select .= "<option value='$date1'";
295 486 100       859 $select .= ' selected="selected"' if($selected);
296 486         927 $select .= ">$date2</option>\n";
297             }
298              
299             # close SELECT tag
300 45         136 $select .= "</select>\n";
301 45         452 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   7900 my $hash = shift;
311 109         164 my $format1 = shift;
312              
313             # set the current date
314 109         2895 my @now = localtime();
315 109         465 my @today = ( $now[3], $now[4]+1, $now[5]+1900 );
316              
317 109         780 %Settings = ();
318 109         169 %Settings = %{ clone(\%Defaults) };
  109         4780  
319 109         630 $Settings{startdate} = encode_date(@today);
320              
321             # if no user hash table provided, lets go
322 109 100       333 return unless($hash);
323              
324 91         361 for my $key1 (keys %$hash) {
325              
326             # store excluded days
327 351 100       1360 if(lc $key1 eq 'exclude') {
    100          
    100          
    100          
    100          
    100          
328 66         127 for my $key2 (keys %{$hash->{$key1}}) {
  66         225  
329 147         272 my $inx = $dotw{lc $key2};
330              
331             # exclude days of the week
332 147 100 100     417 if(defined $inx) {
    100          
    100          
    100          
    100          
333 92         185 $Settings{exclude}{days}->[$inx] = $hash->{$key1}{$key2};
334              
335             # exclude months
336             } elsif($inx = $months{lc $key2}) {
337 19         46 $Settings{exclude}{months}->[$inx] = $hash->{$key1}{$key2};
338              
339             # exclude weekends
340             } elsif(lc $key2 eq 'weekend') {
341 16         99 $Settings{exclude}{days}->[0] = $hash->{$key1}{$key2};
342 16         49 $Settings{exclude}{days}->[6] = $hash->{$key1}{$key2};
343            
344             # exclude weekdays
345             } elsif(lc $key2 eq 'weekday') {
346 15         48 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         7 %{$Settings{holidays}} = map {$_ => 1} @{$hash->{$key1}{$key2}};
  3         12  
  9         19  
  3         7  
351             }
352             }
353              
354             # ensure we aren't wasting time
355 66         133 my $count = 0;
356 66 100       147 foreach my $inx (0..6) { $count++ if($Settings{exclude}{days}->[$inx]) }
  462         974  
357 66 100       156 return 1 if($count == 7);
358 65         112 $count = 0;
359 65 100       125 foreach my $inx (1..12) { $count++ if($Settings{exclude}{months}->[$inx]) }
  780         1363  
360 65 100       187 return 1 if($count == 12);
361              
362             # store selected date
363             } elsif(lc $key1 eq 'select') {
364 39         256 my @dates = ($hash->{$key1} =~ /(\d+)/g);
365 39         124 $Settings{selected} = \@dates;
366              
367             # store start date
368             } elsif(lc $key1 eq 'start') {
369 89         633 my @dates = ($hash->{$key1} =~ /(\d+)/g);
370 89         265 $Settings{startdate} = encode_date(@dates);
371              
372             # store end date
373             } elsif(lc $key1 eq 'end') {
374 76   50     235 $Settings{maxcount} ||= 9999;
375 76         538 my @dates = ($hash->{$key1} =~ /(\d+)/g);
376 76         217 $Settings{enddate} = encode_date(@dates);
377              
378             # store user defined values
379             } elsif(lc $key1 eq 'options') {
380 34         82 $Settings{maxcount} = $hash->{$key1};
381             } elsif(lc $key1 eq 'name') {
382 46         153 $Settings{selectname} = $hash->{$key1};
383             }
384             }
385              
386             # check whether we have a bad start/end dates
387 89 100       305 return 1 if(!$Settings{startdate});
388 88 100 100     464 return 1 if( $Settings{enddate} && compare_dates($Settings{enddate},$Settings{startdate}) < 0);
389 87 100       216 return 1 if(!$Settings{maxcount});
390              
391 85         256 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