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   1874524 use strict;
  13         60  
  13         384  
4 13     13   67 use warnings;
  13         40  
  13         415  
5              
6 13     13   66 use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT_OK @EXPORT);
  13         27  
  13         1881  
7             $VERSION = '1.02';
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   5589 use Calendar::Functions qw(:all);
  13         48  
  13         2652  
89 13     13   6007 use Clone qw(clone);
  13         32622  
  13         722  
90 13     13   6236 use Tie::IxHash;
  13         33222  
  13         20622  
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 37892 my $wantarray = (@_ < 2 || ref($_[1]) eq 'HASH') ? 1 : 0;
151 50         139 my ($fmt1,$fmt2,$hash) = _thelist(@_);
152 50         169 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 33482 my ($fmt1,$fmt2,$hash) = _thelist(@_);
170 45         130 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   634 my ($format1,$format2,$usrhash);
191 96 100       308 $format1 = shift unless(ref($_[0]) eq 'HASH');
192 96 100       235 $format2 = shift unless(ref($_[0]) eq 'HASH');
193 96 100       245 $usrhash = shift if(ref($_[0]) eq 'HASH');
194              
195 96 100       233 $format1 = $Format unless($format1);
196 96 100       259 $format2 = $format1 unless($format2);
197              
198 96 100       230 return if _setargs($usrhash,$format1);
199              
200 95         200 $Settings{nowdate} = $Settings{startdate};
201              
202 95         140 my $optcount = 0; # our option counter
203 95         137 my %DateHash;
204 95         464 tie(%DateHash, 'Tie::IxHash');
205              
206 95         1557 while($optcount < $Settings{maxcount}) {
207 1595         3714 my ($nowday,$nowmon,$nowyear,$nowdow) = decode_date($Settings{nowdate});
208              
209             # ignore days we're not interested in
210 1595 100 100     6270 unless( $Settings{exclude}{days}->[$nowdow]
211             || $Settings{exclude}{months}->[$nowmon]) {
212              
213             # store the date, unless its a holiday
214 1000         4102 my $fdate = sprintf "%02d-%02d-%04d", $nowday,$nowmon,$nowyear;
215             $DateHash{$optcount++} = [decode_date($Settings{nowdate})]
216 1000 100       3022 unless($Settings{holidays}->{$fdate});
217             }
218              
219             # stop if reached end date
220 1595 100       18208 last if(compare_dates($Settings{nowdate},$Settings{enddate}) == 0);
221              
222             # increment
223 1548         3256 $Settings{nowdate} = add_day($Settings{nowdate});
224             }
225              
226 95         388 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   695 my ($fmt1,$fmt2,$hash,$wantarray) = @_;
241 51 100       113 return unless($hash);
242              
243 50         86 my (@returns,%returns);
244 50         196 tie(%returns, 'Tie::IxHash');
245              
246 50         906 foreach my $key (sort {$a <=> $b} keys %$hash) {
  665         3276  
247 508         3349 my $date1 = format_date($fmt1,@{$hash->{$key}});
  508         1664  
248 508 100       1190 if($wantarray) {
249 307         636 push @returns, $date1;
250             } else {
251 201         271 my $date2 = format_date($fmt2,@{$hash->{$key}});
  201         726  
252 201         807 $returns{$date1} = $date2;
253             }
254             }
255              
256 50 100       813 return @returns if($wantarray);
257 21         67 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   505 my ($fmt1,$fmt2,$hash) = @_;
273 46 100       122 return unless($hash);
274              
275             # open SELECT tag
276 45         127 my $select = "<select name='$Settings{selectname}'>\n";
277              
278             # add an OPTION elements
279 45         197 foreach my $key (sort {$a <=> $b} keys %$hash) {
  636         3182  
280 486         781 my $selected = 0;
281              
282             # check whether this option has been selected
283             $selected = 1
284 486         1271 if( @{$Settings{selected}} &&
285             $hash->{$key}->[0] == $Settings{selected}->[0] &&
286             $hash->{$key}->[1] == $Settings{selected}->[1] &&
287 486 100 100     648 $hash->{$key}->[2] == $Settings{selected}->[2]);
      100        
      66        
288              
289             # format date strings
290 486         1450 my $date1 = format_date($fmt1,@{$hash->{$key}});
  486         1582  
291 486         917 my $date2 = format_date($fmt2,@{$hash->{$key}});
  486         1553  
292              
293             # create the option
294 486         1142 $select .= "<option value='$date1'";
295 486 100       948 $select .= ' selected="selected"' if($selected);
296 486         978 $select .= ">$date2</option>\n";
297             }
298              
299             # close SELECT tag
300 45         151 $select .= "</select>\n";
301 45         519 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   7905 my $hash = shift;
311 109         179 my $format1 = shift;
312              
313             # set the current date
314 109         3033 my @now = localtime();
315 109         488 my @today = ( $now[3], $now[4]+1, $now[5]+1900 );
316              
317 109         796 %Settings = ();
318 109         186 %Settings = %{ clone(\%Defaults) };
  109         4624  
319 109         638 $Settings{startdate} = encode_date(@today);
320              
321             # if no user hash table provided, lets go
322 109 100       331 return unless($hash);
323              
324 91         373 for my $key1 (keys %$hash) {
325              
326             # store excluded days
327 351 100       1348 if(lc $key1 eq 'exclude') {
    100          
    100          
    100          
    100          
    100          
328 66         111 for my $key2 (keys %{$hash->{$key1}}) {
  66         277  
329 147         290 my $inx = $dotw{lc $key2};
330              
331             # exclude days of the week
332 147 100 100     434 if(defined $inx) {
    100          
    100          
    100          
    100          
333 92         194 $Settings{exclude}{days}->[$inx] = $hash->{$key1}{$key2};
334              
335             # exclude months
336             } elsif($inx = $months{lc $key2}) {
337 19         50 $Settings{exclude}{months}->[$inx] = $hash->{$key1}{$key2};
338              
339             # exclude weekends
340             } elsif(lc $key2 eq 'weekend') {
341 16         48 $Settings{exclude}{days}->[0] = $hash->{$key1}{$key2};
342 16         46 $Settings{exclude}{days}->[6] = $hash->{$key1}{$key2};
343            
344             # exclude weekdays
345             } elsif(lc $key2 eq 'weekday') {
346 15         51 for my $index (1..5) { $Settings{exclude}{days}->[$index] = $hash->{$key1}{$key2}; }
  75         145  
347            
348             # check for holiday setting
349             } elsif(lc $key2 eq 'holidays' and ref($hash->{$key1}{$key2}) eq 'ARRAY') {
350 3         6 %{$Settings{holidays}} = map {$_ => 1} @{$hash->{$key1}{$key2}};
  3         13  
  9         18  
  3         8  
351             }
352             }
353              
354             # ensure we aren't wasting time
355 66         134 my $count = 0;
356 66 100       148 foreach my $inx (0..6) { $count++ if($Settings{exclude}{days}->[$inx]) }
  462         882  
357 66 100       180 return 1 if($count == 7);
358 65         105 $count = 0;
359 65 100       109 foreach my $inx (1..12) { $count++ if($Settings{exclude}{months}->[$inx]) }
  780         1366  
360 65 100       175 return 1 if($count == 12);
361              
362             # store selected date
363             } elsif(lc $key1 eq 'select') {
364 39         296 my @dates = ($hash->{$key1} =~ /(\d+)/g);
365 39         136 $Settings{selected} = \@dates;
366              
367             # store start date
368             } elsif(lc $key1 eq 'start') {
369 90         612 my @dates = ($hash->{$key1} =~ /(\d+)/g);
370 90         249 $Settings{startdate} = encode_date(@dates);
371              
372             # store end date
373             } elsif(lc $key1 eq 'end') {
374 75   50     212 $Settings{maxcount} ||= 9999;
375 75         473 my @dates = ($hash->{$key1} =~ /(\d+)/g);
376 75         220 $Settings{enddate} = encode_date(@dates);
377              
378             # store user defined values
379             } elsif(lc $key1 eq 'options') {
380 34         91 $Settings{maxcount} = $hash->{$key1};
381             } elsif(lc $key1 eq 'name') {
382 46         131 $Settings{selectname} = $hash->{$key1};
383             }
384             }
385              
386             # check whether we have a bad start/end dates
387 89 100       307 return 1 if(!$Settings{startdate});
388 88 100 100     508 return 1 if( $Settings{enddate} && compare_dates($Settings{enddate},$Settings{startdate}) < 0);
389 87 100       227 return 1 if(!$Settings{maxcount});
390              
391 85         266 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             =head2 Further Modules
518              
519             =over 4
520              
521             =item L<Calendar::Functions>
522              
523             =back
524              
525             =head2 Date/Time Modules
526              
527             =over 4
528              
529             =item L<Date::ICal>
530              
531             =item L<DateTime>
532              
533             =item L<Time::Local>
534              
535             =item L<Time::Piece>
536              
537             =back
538              
539             =head2 Further Information
540              
541             =over 4
542              
543             =item L<The Calendar FAQ>
544              
545             L<http://www.tondering.dk/claus/calendar.html>
546              
547             =item L<The Perl Advent Entry>
548              
549             2018-12-01 : L<http://perladvent.org/2018/2018-12-01.html>
550              
551             =back
552            
553             =head1 BUGS, PATCHES & FIXES
554              
555             There are no known bugs at the time of this release. However, if you spot a
556             bug or are experiencing difficulties that are not explained within the POD
557             documentation, please submit a bug to the RT system (see link below). However,
558             it would help greatly if you are able to pinpoint problems or even supply a
559             patch.
560              
561             Fixes are dependent upon their severity and my availability. Should a fix not
562             be forthcoming, please feel free to (politely) remind me by sending an email
563             to barbie@cpan.org .
564              
565             RT: http://rt.cpan.org/Public/Dist/Display.html?Name=Calendar-List
566              
567             =head1 AUTHOR
568              
569             Barbie, <barbie@cpan.org>
570             for Miss Barbell Productions <http://www.missbarbell.co.uk>.
571              
572             =head1 THANKS TO
573              
574             Dave Cross, E<lt>dave at dave.orgE<gt> for creating Calendar::Simple, the
575             newbie poster on a technical message board who inspired me to write the
576             original code and Richard Clamp E<lt>richardc at unixbeard.co.ukE<gt>
577             for testing the beta versions.
578              
579             =head1 COPYRIGHT AND LICENSE
580              
581             Copyright (C) 2003-2019 Barbie for Miss Barbell Productions
582              
583             This distribution is free software; you can redistribute it and/or
584             modify it under the Artistic License v2.
585              
586             =cut