File Coverage

blib/lib/TaskForest/Calendar.pm
Criterion Covered Total %
statement 115 115 100.0
branch 63 64 98.4
condition 35 42 83.3
subroutine 11 11 100.0
pod 0 3 0.0
total 224 235 95.3


line stmt bran cond sub pod time code
1             ################################################################################
2             #
3             # $Id: Calendar.pm 211 2009-05-25 06:05:50Z aijaz $
4             #
5             ################################################################################
6              
7             =head1 NAME
8              
9             TaskForest::Calendar --
10              
11             =head1 SYNOPSIS
12              
13             use TaskForest::LocalTime;
14              
15             my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = &LocalTime::localtime();
16             #
17             # THE MONTH IS 1-BASED, AND THE YEAR IS THE FULL YEAR
18             # (i.e., $mon++; $year += 1900; is not required)
19              
20             &LocalTime::setTime({ year => $year,
21             month => $mon,
22             day => $day,
23             hour => $hour,
24             min => $min,
25             sec => $sec,
26             tz => $tz
27             });
28             # ...
29             ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = &LocalTime::localtime();
30             #
31             # THE MONTH IS 1-BASED, AND THE YEAR IS THE FULL YEAR
32             # (i.e., $mon++; $year += 1900; is not required)
33              
34             =head1 DOCUMENTATION
35              
36             If you're just looking to use the taskforest application, the only
37             documentation you need to read is that for TaskForest. You can do this
38             either of the two ways:
39              
40             perldoc TaskForest
41              
42             OR
43              
44             man TaskForest
45              
46             =head1 DESCRIPTION
47              
48             This is a simple package that provides support for Calendar functions
49              
50             =head1 METHODS
51              
52             =cut
53              
54             package TaskForest::Calendar;
55 94     94   15254 use strict;
  94         201  
  94         7817  
56 94     94   764 use warnings;
  94         354  
  94         4191  
57 94     94   739 use Carp;
  94         169  
  94         7581  
58 94     94   5284 use DateTime;
  94         597440  
  94         2153  
59 94     94   3153 use Time::Local;
  94         5105  
  94         5383  
60 94     94   564 use Data::Dumper;
  94         200  
  94         5912  
61              
62             BEGIN {
63 94     94   497 use vars qw($VERSION);
  94         205  
  94         7975  
64 94     94   146692 $VERSION = '1.30';
65             }
66              
67              
68             my $time_offset = 0;
69              
70             # ------------------------------------------------------------------------------
71             =pod
72              
73             =over 4
74              
75             =item setTime()
76              
77             Usage : &LocalTime::setTime({ year => $year,
78             month => $mon,
79             day => $day,
80             hour => $hour,
81             min => $min,
82             sec => $sec,
83             tz => $tz
84             });
85             Purpose : This method 'sets' the current time to the time specified, in the
86             timezone specified.
87             Returns : Nothing
88             Argument : A hash of values
89             Throws : Nothing
90              
91             =back
92              
93             =cut
94              
95             # ------------------------------------------------------------------------------
96             sub canRunToday {
97 807     807 0 11690175 my $args = shift;
98              
99 807         1573 my $rules = $args->{rules};
100 807         1355 my $tz = $args->{tz};
101              
102 807         2330 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = &TaskForest::LocalTime::ft($tz);
103              
104             # for each rule, see if today applies (yes or no or inconclusive).
105             # default is no.
106             # last matching rule that returns yes or no wins
107              
108 807         26178 my $today_hash = {
109             sec => $sec,
110             min => $min,
111             hour => $hour,
112             mday => $mday,
113             mon => $mon,
114             year => $year,
115             wday => $wday,
116             yday => $yday,
117             isdst => $isdst,
118             };
119              
120 807         1689 my $ok = '-';
121 807         1591 foreach my $rule (@$rules) {
122 892         1836 $rule =~ s/\#.*//;
123 892 100       4060 next unless $rule =~ /\S/;
124 815         1889 my $match = doesRuleMatch($today_hash, $rule);
125 815 100 100     4778 if ($match eq '+' or $match eq '-') {
    100          
126 89         210 $ok = $match;
127             }
128             elsif ($match eq 'N/A') {
129             # not applicable - do nothing
130             }
131             else {
132 709         4225 return $match;
133             }
134             }
135              
136 98         648 return $ok;
137             }
138              
139              
140             sub doesRuleMatch {
141 815     815 0 1455 my ($today, $rule) = @_;
142              
143             # [+|-] ( [ [first | second | third | fourth | fifth] [last] DOW ] | (YYYY|*)/(MM|*)/(DD|*) )
144             # trim white space
145 815         2814 $rule =~ s/^\s+//;
146 815         3816 $rule =~ s/\s+$//;
147 815         1529 $rule =~ tr/A-Z/a-z/;
148            
149 815         3145 my @components = split(/\s+/, $rule);
150 815 50       2203 return "No components" unless (@components);
151              
152 815         1270 my $plus_or_minus = '+';
153              
154             # if +/- isn't defined, assume it's a +
155             #
156 815 100 100     2452 if ($components[0] eq '+' || $components[0] eq '-') {
157 789         1374 $plus_or_minus = shift(@components);
158             }
159 815 100       1642 return "No components after plus or minus" unless (@components);
160              
161            
162 810         1155 my $nth = undef;
163 810         840 my $dow = undef;
164 810         4868 my %offsets = ( first => 1, second => 2, third => 3, fourth => 4, fifth => 5, last => -1, every => 0, );
165 810         3848 my %dows = ( sun => 0 , mon => 1, tue => 2, wed => 3, thu => 4, fri => 5, sat => 6, );
166            
167             # if the second item is _/_/_, then assume that there is no nth DOW
168            
169 810 100       2616 if (defined $offsets{$components[0]}) {
170 730 100       1646 return "No components after offset" if (scalar(@components) < 2);
171            
172 723         1361 $nth = $offsets{$components[0]};
173            
174 723 100       1564 if ($components[1] eq 'last') {
175 307 100       753 $nth = ($nth > 0)? $nth * -1 : -1;
176 307         574 splice(@components, 1, 1); # get rid of 'last'
177             }
178 723 100       1609 return "No components after offset last" if (scalar(@components) < 2);
179            
180 716         1399 $dow = $dows{substr($components[1], 0, 3)};
181              
182             # now get rid of the first 2
183 716         10358 splice(@components, 0, 2);
184             }
185              
186 796         1026 my ($y, $m, $d);
187 796 100       1575 if ($components[0]) {
188 698         951 my $yyyymmdd = $components[0];
189 698         2108 my ($y, $m, $d) = split(/\//, $yyyymmdd);
190              
191 698 100       1802 if (defined $nth) {
192 618 100       4175 return "Date of month not allowed when specifying day of week" if $d; # can't have last Friday in 2009/November/1
193 226         349 $d = '*'; # do this to make the check for keep_going easier
194             }
195 306 100 66     2375 return "Date not specified in a valid format" unless ($y && $m && $d);
      66        
196              
197 305 100       649 if ($y ne '*') { $y *= 1; if ($y < 1970 ) { return "Invalid year"; } }
  249 100       403  
  249         516  
  99         568  
198 206 100 66     500 if ($m ne '*') { $m *= 1; if ($m < 1 || $m > 12 ) { return "Invalid month"; } }
  152 100       201  
  152         680  
  99         608  
199 107 100 66     386 if ($d ne '*') { $d *= 1; if ($d < 1 || $d > 31 ) { return "Invalid day"; } }
  16 100       19  
  16         67  
  1         7  
200              
201              
202             # now try to eliminate based on yyyy mm and dd
203              
204 106         131 my $keep_going;
205              
206 106 100 100     1211 if ( ($y eq '*' || $y == $today->{year})
      100        
      66        
      100        
      66        
207             &&
208             ($m eq '*' || $m == $today->{mon})
209             &&
210             ($d eq '*' || $d == $today->{mday})
211             )
212             {
213 95         134 $keep_going = 1;
214 95         162 $y = $today->{year};
215 95         136 $m = $today->{mon};
216 95         177 $d = $today->{mday};
217             }
218             else {
219 11         14 $keep_going = 0;
220             }
221              
222 106 100       311 return 'N/A' unless $keep_going;
223             #return '-' unless $keep_going;
224              
225             # now we know that the date part matches.
226             # now check for the day of week part, if present
227              
228 95 100 66     470 if (defined $nth && defined $dow) {
229             # $nth could be 0 (every)
230              
231 28 100       56 if ($dow == $today->{wday}) {
232             # check nth. Check easy ones first
233             #
234 26 100       53 if ($nth == 0) { return $plus_or_minus; }
  2         11  
235              
236             # find days of week
237 24         58 my $dates = findDaysOfWeek($y, $m, $dow);
238              
239 24 100       59 if ($nth > 0) { $nth--; } # so we can use it as an array subscript
  13         16  
240              
241 24 100 100     71 return '-' if $nth == 4 and scalar(@$dates) < 5; # If the fifth dow does exist
242            
243 23 100       56 if ($dates->[$nth] == $today->{mday}) {
244 19         115 return $plus_or_minus;
245             }
246             else {
247             #return '-';
248 4         28 return 'N/A';
249             }
250             }
251             else {
252             #return '-';
253 2         13 return 'N/A';
254             }
255             }
256             else {
257 67         380 return $plus_or_minus;
258             }
259            
260             }
261              
262 98         519 return 'Applicable date range not present';
263              
264             }
265              
266             # returns an array of 4 or 5 mdays, each of which correspond to the nth dow of y/m
267             sub findDaysOfWeek {
268 8417     8417 0 83144928 my ($y, $m, $dow) = @_;
269              
270             # find the first dow
271             #my ($sec1,$min1,$hour1,$mday1,$mon1,$year1,$wday1,$yday1,$isdst1) = localtime(timelocal(0, 0, 0, 1, $m - 1, $y - 1900));
272 8417         43002 my $dt = DateTime->new(year => $y,
273             month => $m,
274             day => 1,
275             hour => 0,
276             minute => 0,
277             second => 0,
278             );
279 8417         13734533 my $wday1 = $dt->day_of_week;
280 8417 100       49700 $wday1 = 0 if $wday1 == 7;
281            
282             # dow $wday1 transform
283             # 3 0 + 3 = 3
284             # 3 1 + (3 - 1) = 2
285             # 3 2 + (3 - 2) = 1
286             # 3 3 + (3 - 3) = 0
287             # 3 4 + (3 - 4) = -1 + 7 = 6
288             # 3 5 + (3 - 5) = -2 + 7 = 5
289             # 3 6 + (3 - 6) = -3 + 7 = 4
290             # 0 0 0
291             # 0 1 0 - 1 + 7 = 6
292              
293 8417         15300 my @result = ();
294 8417 100       35598 $result[0] = ($dow >= $wday1) ? $dow - $wday1 + 1 : $dow - $wday1 + 1 + 7;
295              
296 8417         36072 my @days_in_month = (-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
297 8417 100 100     26744 if ($m == 2 and $dt->is_leap_year()) {
298             #$days_in_month[2] += ($y % 4) ? 0 : ($y % 100) ? 1 : ($y % 400) ? 0: 1;
299 184         1780 $days_in_month[2] ++;
300             }
301              
302 8417         16386 my $days_in_month = $days_in_month[$m];
303              
304 8417         9759 my $next = 0;
305 8417         34480 for (my $next = $result[0] + 7; $next <= $days_in_month; $next += 7) {
306 28183         81466 push(@result, $next);
307             }
308              
309 8417         79748 return (\@result);
310             }
311              
312             1;