File Coverage

blib/lib/Calendar/Simple.pm
Criterion Covered Total %
statement 69 69 100.0
branch 28 28 100.0
condition 19 20 95.0
subroutine 13 13 100.0
pod 2 2 100.0
total 131 132 99.2


line stmt bran cond sub pod time code
1             # $Id$
2              
3             =head1 NAME
4              
5             Calendar::Simple - Perl extension to create simple calendars
6              
7             =head1 SYNOPSIS
8              
9             use Calendar::Simple;
10              
11             my @curr = calendar; # get current month
12             my @this_sept = calendar(9); # get 9th month of current year
13             my @sept_2002 = calendar(9, 2002); # get 9th month of 2002
14             my @monday = calendar(9, 2002, 1); # get 9th month of 2002,
15             # weeks start on Monday
16              
17             my @span = date_span(mon => 10, # returns span of dates
18             year => 2006,
19             begin => 15,
20             end => 28);
21              
22             =cut
23              
24             package Calendar::Simple;
25              
26 3     3   2383 use 5.006;
  3         10  
27 3     3   17 use strict;
  3         6  
  3         55  
28 3     3   15 use warnings;
  3         6  
  3         102  
29              
30 3     3   17 use base 'Exporter';
  3         6  
  3         469  
31              
32             our @EXPORT = qw(calendar);
33             our @EXPORT_OK = qw(date_span);
34             our $VERSION = '2.0.3';
35              
36 3     3   1562 use Time::Local;
  3         7031  
  3         175  
37 3     3   21 use Carp;
  3         6  
  3         2387  
38              
39 3     3   2958 eval 'use DateTime';
  3         1721846  
  3         81  
40             my $dt = ! $@;
41             $dt = 0 if $ENV{CAL_SIMPLE_NO_DT};
42              
43             my @days = qw(31 xx 31 30 31 30 31 31 30 31 30 31);
44              
45             =head1 DESCRIPTION
46              
47             A very simple module that exports one function called C<calendar>.
48              
49             =head2 calendar
50              
51             This function returns a data structure representing the dates in a month.
52             The data structure returned is an array of array references. The first
53             level array represents the weeks in the month. The second level array
54             contains the actual days. By default, each week starts on a Sunday and
55             the value in the array is the date of that day. Any days at the beginning
56             of the first week or the end of the last week that are from the previous or
57             next month have the value C<undef>.
58              
59             If the month or year parameters are omitted then the current month or
60             year are assumed.
61              
62             A third, optional parameter, start_day, allows you to set the day each
63             week starts with, with the same values as localtime sets for wday
64             (namely, 0 for Sunday, 1 for Monday and so on).
65              
66             =cut
67              
68             sub calendar {
69 31     31 1 31005 my ($mon, $year, $start_day) = _validate_params(@_);
70              
71 22         70 my $first = _get_first($mon, $year, $start_day);
72              
73 22         60 my @mon = (1 .. _days($mon, $year));
74              
75 22         107 my @first_wk = (undef) x 7;
76 22         72 @first_wk[$first .. 6] = splice @mon, 0, 6 - $first + 1;
77              
78 22         49 my @month = (\@first_wk);
79              
80 22         69 while (my @wk = splice @mon, 0, 7) {
81 92         247 push @month, \@wk;
82             }
83              
84 22         31 $#{$month[-1]} = 6;
  22         88  
85              
86 22 100       145 return wantarray ? @month : \@month;
87             }
88              
89             =head2 date_span
90              
91             This function returns a cut-down version of a month data structure which
92             begins and ends on dates other than the first and last dates of the month.
93             Any weeks that fall completely outside of the date range are removed from
94             the structure and any days within the remaining weeks that fall outside
95             of the date range are set to C<undef>.
96              
97             As there are a number of parameters to this function, they are passed
98             using a named parameter interface. The parameters are as follows:
99              
100             =over 4
101              
102             =item year
103              
104             The required year. Defaults to the current year if omitted.
105              
106             =item mon
107              
108             The required month. Defaults to the current month if omitted.
109              
110             =item begin
111              
112             The first day of the required span. Defaults to the first if omitted.
113              
114             =item end
115              
116             The last day of the required span. Defaults to the last day of the month
117             if omitted.
118              
119             =item start_day
120              
121             Indicates the day of the week that each week starts with. This takes the same
122             values as the optional third parameter to C<calendar>. The default is 1
123             (for Monday).
124              
125             B<NOTE:> As of version 2.0.0, the default C<start_day> has changed. Previously,
126             it was Sunday; now, it is Monday. This is so the default behaviour matches
127             that of the standard Unix C<cal> command.
128              
129             =back
130              
131             This function isn't exported by default, so in order to use it in your
132             program you need to use the module like this:
133              
134             use Calendar::Simple 'date_span';
135              
136             =cut
137              
138             sub date_span {
139 3     3 1 5344 my %params = @_;
140              
141             my ($mon, $year, $start_day) = _validate_params(
142 3         13 @params{ qw[mon year start_day] },
143             );
144              
145 3   100     21 my $begin = $params{begin} || 1;
146 3   66     13 my $end = $params{end} || _days($mon, $year);
147              
148 3         8 my @cal = calendar($mon, $year, $start_day);
149              
150 3         16 shift @cal while $cal[0][6] < $begin;
151              
152 3         6 my $i = 0;
153 3   100     25 while (defined $cal[0][$i] and $cal[0][$i] < $begin) {
154 7         26 $cal[0][$i++] = undef;
155             }
156              
157 3         12 pop @cal while $cal[-1][0] > $end;
158              
159 3         7 $i = -1;
160 3   100     13 while (defined $cal[-1][$i] and $cal[-1][$i] > $end) {
161 6         17 $cal[-1][$i--] = undef;
162             }
163              
164 3         18 return @cal;
165             }
166              
167             sub _get_first {
168 22     22   39 my ($mon, $year, $start_day) = @_;
169              
170 22         38 my $first;
171              
172 22 100       52 if ($dt) {
173 14         71 $first = DateTime->new(year => $year,
174             month => $mon,
175             day => 1)->day_of_week % 7;
176             } else {
177 8         32 $first = (localtime timelocal 0, 0, 0, 1, $mon -1, $year)[6];
178             }
179              
180 22         5239 $first -= $start_day;
181 22 100       66 $first += 7 if ($first < 0);
182              
183 22         44 return $first;
184             }
185              
186             sub _days {
187 23     23   49 my ($mon, $yr) = @_;
188              
189 23 100       109 return $days[$mon - 1] unless $mon == 2;
190 8 100       20 return _isleap($yr) ? 29 : 28;
191             }
192              
193             sub _isleap {
194 8 100   8   30 return 1 unless $_[0] % 400;
195 6 100       16 return unless $_[0] % 100;
196 5 100       21 return 1 unless $_[0] % 4;
197 2         10 return;
198             }
199              
200             sub _validate_params {
201 34     34   73 my ($mon, $year, $start_day) = @_;
202              
203 34         909 my @now = (localtime)[4, 5];
204              
205 34 100       153 $mon = ($now[0] + 1) unless $mon;
206 34 100       85 $year = ($now[1] + 1900) unless $year;
207 34 100       84 $start_day = 1 unless defined $start_day;
208              
209 34 100 100     215 croak "Year $year out of range" if $year < 1970 && !$dt;
210 33 100 100     802 croak "Month $mon out of range" if ($mon < 1 || $mon > 12);
211 29 100 100     549 croak "Start day $start_day out of range"
212             if ($start_day < 0 || $start_day > 6);
213              
214 25         79 return ($mon, $year, $start_day);
215             }
216              
217             1;
218             __END__
219              
220             =head2 EXAMPLE
221              
222             A simple C<cal> replacement would therefore look like this:
223              
224             #!/usr/bin/perl
225              
226             use strict;
227             use warnings;
228             use Calendar::Simple;
229              
230             my @months = qw(January February March April May June July August
231             September October November December);
232              
233             my $mon = shift || (localtime)[4] + 1;
234             my $yr = shift || (localtime)[5] + 1900;
235             my $sd = shift;
236             $ds = 1 unless defined $sd;
237              
238             my @month = calendar($mon, $yr, $sd);
239             print "\n$months[$mon -1] $yr\n\n";
240              
241             my @days = qw(Su Mo Tu We Th Fr Sa);
242             push @days, splice @days, 0, $sd;
243             print "@days\n";
244              
245             foreach (@month) {
246             print map { $_ ? sprintf "%2d ", $_ : ' ' } @$_;
247             print "\n";
248             }
249              
250             A version of this example, called C<pcal>, is installed when you install this
251             module.
252              
253             =head2 Date Range
254              
255             This module will make use of L<DateTime> if it is installed. By using
256             L<DateTime> it can use any date that C<DateTime> can represent. If L<DateTime>
257             is not installed it uses Perl's built-in date handling and therefore
258             can't deal with dates before 1970 and it will also have problems with dates
259             after 2038 on a 32-bit machine.
260              
261             =head2 EXPORT
262              
263             C<calendar>
264              
265             =head1 AUTHOR
266              
267             Dave Cross <dave@mag-sol.com>
268              
269             =head1 ACKNOWLEDGEMENTS
270              
271             With thanks to Paul Mison <cpan@husk.org> for the start day patch.
272              
273             =head1 COPYRIGHT
274              
275             Copyright (C) 2002-2008, Magnum Solutions Ltd. All Rights Reserved.
276              
277             =head1 LICENSE
278              
279             This script is free software; you can redistribute it and/or
280             modify it under the same terms as Perl itself.
281              
282             =head1 SEE ALSO
283              
284             L<perl>, L<localtime>, L<DateTime>
285              
286             =cut