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   2688 use 5.006;
  3         13  
27 3     3   16 use strict;
  3         11  
  3         65  
28 3     3   20 use warnings;
  3         14  
  3         104  
29              
30 3     3   17 use base 'Exporter';
  3         6  
  3         486  
31              
32             our @EXPORT = qw(calendar);
33             our @EXPORT_OK = qw(date_span);
34             our $VERSION = '2.0.2';
35              
36 3     3   1580 use Time::Local;
  3         7237  
  3         184  
37 3     3   21 use Carp;
  3         6  
  3         2548  
38              
39 3     3   3034 eval 'use DateTime';
  3         1711363  
  3         62  
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 34878 my ($mon, $year, $start_day) = _validate_params(@_);
70              
71 22         66 my $first = _get_first($mon, $year, $start_day);
72              
73 22         58 my @mon = (1 .. _days($mon, $year));
74              
75 22         73 my @first_wk = (undef) x 7;
76 22         73 @first_wk[$first .. 6] = splice @mon, 0, 6 - $first + 1;
77              
78 22         45 my @month = (\@first_wk);
79              
80 22         74 while (my @wk = splice @mon, 0, 7) {
81 96         253 push @month, \@wk;
82             }
83              
84 22         39 $#{$month[-1]} = 6;
  22         118  
85              
86 22 100       165 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 5495 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     24 my $begin = $params{begin} || 1;
146 3   66     12 my $end = $params{end} || _days($mon, $year);
147              
148 3         9 my @cal = calendar($mon, $year, $start_day);
149              
150 3         15 shift @cal while $cal[0][6] < $begin;
151              
152 3         4 my $i = 0;
153 3   100     26 while (defined $cal[0][$i] and $cal[0][$i] < $begin) {
154 7         26 $cal[0][$i++] = undef;
155             }
156              
157 3         13 pop @cal while $cal[-1][0] > $end;
158              
159 3         8 $i = -1;
160 3   100     12 while (defined $cal[-1][$i] and $cal[-1][$i] > $end) {
161 6         19 $cal[-1][$i--] = undef;
162             }
163              
164 3         17 return @cal;
165             }
166              
167             sub _get_first {
168 22     22   39 my ($mon, $year, $start_day) = @_;
169              
170 22         37 my $first;
171              
172 22 100       55 if ($dt) {
173 14         66 $first = DateTime->new(year => $year,
174             month => $mon,
175             day => 1)->day_of_week % 7;
176             } else {
177 8         30 $first = (localtime timelocal 0, 0, 0, 1, $mon -1, $year)[6];
178             }
179              
180 22         5267 $first -= $start_day;
181 22 100       61 $first += 7 if ($first < 0);
182              
183 22         48 return $first;
184             }
185              
186             sub _days {
187 23     23   47 my ($mon, $yr) = @_;
188              
189 23 100       117 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   28 return 1 unless $_[0] % 400;
195 6 100       17 return unless $_[0] % 100;
196 5 100       33 return 1 unless $_[0] % 4;
197 2         10 return;
198             }
199              
200             sub _validate_params {
201 34     34   78 my ($mon, $year, $start_day) = @_;
202              
203 34         951 my @now = (localtime)[4, 5];
204              
205 34 100       153 $mon = ($now[0] + 1) unless $mon;
206 34 100       88 $year = ($now[1] + 1900) unless $year;
207 34 100       79 $start_day = 1 unless defined $start_day;
208              
209 34 100 100     212 croak "Year $year out of range" if $year < 1970 && !$dt;
210 33 100 100     772 croak "Month $mon out of range" if ($mon < 1 || $mon > 12);
211 29 100 100     546 croak "Start day $start_day out of range"
212             if ($start_day < 0 || $start_day > 6);
213              
214 25         93 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              
236             my @month = calendar($mon, $yr);
237              
238             print "\n$months[$mon -1] $yr\n\n";
239             print "Su Mo Tu We Th Fr Sa\n";
240             foreach (@month) {
241             print map { $_ ? sprintf "%2d ", $_ : ' ' } @$_;
242             print "\n";
243             }
244              
245             A version of this example, called C<pcal>, is installed when you install this
246             module.
247              
248             =head2 Date Range
249              
250             This module will make use of L<DateTime> if it is installed. By using
251             L<DateTime> it can use any date that C<DateTime> can represent. If L<DateTime>
252             is not installed it uses Perl's built-in date handling and therefore
253             can't deal with dates before 1970 and it will also have problems with dates
254             after 2038 on a 32-bit machine.
255              
256             =head2 EXPORT
257              
258             C<calendar>
259              
260             =head1 AUTHOR
261              
262             Dave Cross <dave@mag-sol.com>
263              
264             =head1 ACKNOWLEDGEMENTS
265              
266             With thanks to Paul Mison <cpan@husk.org> for the start day patch.
267              
268             =head1 COPYRIGHT
269              
270             Copyright (C) 2002-2008, Magnum Solutions Ltd. All Rights Reserved.
271              
272             =head1 LICENSE
273              
274             This script is free software; you can redistribute it and/or
275             modify it under the same terms as Perl itself.
276              
277             =head1 SEE ALSO
278              
279             L<perl>, L<localtime>, L<DateTime>
280              
281             =cut