File Coverage

blib/lib/DateTime/Event/Cron.pm
Criterion Covered Total %
statement 340 384 88.5
branch 133 176 75.5
condition 33 62 53.2
subroutine 69 80 86.2
pod 14 21 66.6
total 589 723 81.4


line stmt bran cond sub pod time code
1             package DateTime::Event::Cron;
2              
3 6     6   1557865 use 5.006;
  6         25  
  6         244  
4 6     6   35 use strict;
  6         16  
  6         224  
5 6     6   32 use warnings;
  6         13  
  6         212  
6 6     6   33 use Carp;
  6         10  
  6         675  
7              
8 6     6   30 use vars qw($VERSION);
  6         11  
  6         386  
9              
10             $VERSION = '0.08';
11              
12 6     6   31 use constant DEBUG => 0;
  6         11  
  6         437  
13              
14 6     6   1561 use DateTime;
  6         183454  
  6         133  
15 6     6   6531 use DateTime::Set;
  6         375723  
  6         213  
16 6     6   5781 use Set::Crontab;
  6         6236  
  6         24943  
17              
18             my %Object_Attributes;
19              
20             ###
21              
22             sub from_cron {
23             # Return cron as DateTime::Set
24 29     29 1 34453 my $class = shift;
25 29 50       173 my %sparms = @_ == 1 ? (cron => shift) : @_;
26 29         46 my %parms;
27 29         91 $parms{cron} = delete $sparms{cron};
28 29         95 $parms{user_mode} = delete $sparms{user_mode};
29 29 100       439 $parms{cron} or croak "Cron string parameter required.\n";
30 27         129 my $dtc = $class->new(%parms);
31 22         93 $dtc->as_set(%sparms);
32             }
33              
34             sub from_crontab {
35             # Return list of DateTime::Sets based on entries from
36             # a crontab file.
37 1     1 1 18 my $class = shift;
38 1 50       8 my %sparms = @_ == 1 ? (file => shift) : @_;
39 1         4 my $file = delete $sparms{file};
40 1         3 delete $sparms{cron};
41 1         5 my $fh = $class->_prepare_fh($file);
42 1         3 my @cronsets;
43 1         5 while (<$fh>) {
44 11         15 chomp;
45 11         14 my $set;
46 11         16 eval { $set = $class->from_cron(%sparms, cron => $_) };
  11         35  
47 11 100 66     1078 push(@cronsets, $set) if ref $set && !$@;
48             }
49 1         6 @cronsets;
50             }
51              
52             sub as_set {
53             # Return self as DateTime::Set
54 22     22 1 36 my $self = shift;
55 22         48 my %sparms = @_;
56 22 50 33     182 Carp::cluck "Recurrence callbacks overriden by ". ref $self . "\n"
      33        
57             if $sparms{next} || $sparms{recurrence} || $sparms{previous};
58 22         37 delete $sparms{next};
59 22         37 delete $sparms{previous};
60 22         32 delete $sparms{recurrence};
61 22     83   127 $sparms{next} = sub { $self->next(@_) };
  83         76307  
62 22     65   88 $sparms{previous} = sub { $self->previous(@_) };
  65         105529  
63 22         196 DateTime::Set->from_recurrence(%sparms);
64             }
65              
66             ###
67              
68             sub new {
69 48     48 0 12502 my $class = shift;
70 48         155 my $self = {};
71 48         255 bless $self, $class;
72 48 100       313 my %parms = @_ == 1 ? (cron => shift) : @_;
73 48         197 my $crontab = $self->_make_cronset(%parms);
74 27         106 $self->_cronset($crontab);
75 27         88 $self;
76             }
77              
78 1     1 1 1347 sub new_from_cron { new(@_) }
79              
80             sub new_from_crontab {
81 0     0 1 0 my $class = shift;
82 0 0       0 my %parms = @_ == 1 ? (file => shift()) : @_;
83 0         0 my $fh = $class->_prepare_fh($parms{file});
84 0         0 delete $parms{file};
85 0         0 my @dtcrons;
86 0         0 while (<$fh>) {
87 0         0 my $dtc;
88 0         0 eval { $dtc = $class->new(%parms, cron => $_) };
  0         0  
89 0 0 0     0 if (ref $dtc && !$@) {
90 0         0 push(@dtcrons, $dtc);
91 0 0       0 $parms{user_mode} = 1 if defined $dtc->user;
92             }
93             }
94 0         0 @dtcrons;
95             }
96              
97             ###
98              
99             sub _prepare_fh {
100 1     1   2 my $class = shift;
101 1         2 my $fh = shift;
102 1 50       6 if (! ref $fh) {
103 0         0 my $file = $fh;
104 0         0 local(*FH);
105 0         0 $fh = do { local *FH; *FH }; # doubled *FH avoids warning
  0         0  
  0         0  
106 0 0       0 open($fh, "<$file")
107             or croak "Error opening $file for reading\n";
108             }
109 1         23 $fh;
110             }
111              
112             ###
113              
114             sub valid {
115             # Is the given date valid according the current cron settings?
116 596     596 1 69839 my($self, $date) = @_;
117 596 100 66     2038 return if !$date || $date->second;
118 584 100 100     26921 $self->minute->contains($date->minute) &&
      100        
119             $self->hour->contains($date->hour) &&
120             $self->days_contain($date->day, $date->dow) &&
121             $self->month->contains($date->month);
122             }
123              
124             sub match {
125             # Does the given date match the cron spec?
126 0     0 1 0 my($self, $date) = @_;
127 0 0       0 $date = DateTime->now unless $date;
128 0 0 0     0 $self->minute->contains($date->minute) &&
      0        
129             $self->hour->contains($date->hour) &&
130             $self->days_contain($date->day, $date->dow) &&
131             $self->month->contains($date->month);
132             }
133              
134             ### Return adjacent dates without altering original date
135              
136             sub next {
137 96     96 1 14508 my($self, $date) = @_;
138 96 100       389 $date = DateTime->now unless $date;
139 96         17245 $self->increment($date->clone);
140             }
141              
142             sub previous {
143 78     78 1 19683 my($self, $date) = @_;
144 78 100       1069 $date = DateTime->now unless $date;
145 78         4470 $self->decrement($date->clone);
146             }
147              
148             ### Change given date to adjacent dates
149              
150             sub increment {
151 96     96 1 1267 my($self, $date) = @_;
152 96 50       302 $date = DateTime->now unless $date;
153 96 100       4472 return $date if $date->is_infinite;
154 74         387 do {
155 74         340 $self->_attempt_increment($date);
156             } until $self->valid($date);
157 74         2023 $date;
158             }
159              
160             sub decrement {
161 78     78 1 1199 my($self, $date) = @_;
162 78 50       627 $date = DateTime->now unless $date;
163 78 100       4463 return $date if $date->is_infinite;
164 56         304 do {
165 56         196 $self->_attempt_decrement($date);
166             } until $self->valid($date);
167 56         1191 $date;
168             }
169              
170             ###
171              
172             sub _attempt_increment {
173 74     74   125 my($self, $date) = @_;
174 74 50       236 ref $date or croak "Reference to datetime object reqired\n";
175 74 100       370 $self->valid($date) ?
176             $self->_valid_incr($date) :
177             $self->_invalid_incr($date);
178             }
179              
180             sub _attempt_decrement {
181 56     56   108 my($self, $date) = @_;
182 56 50       187 ref $date or croak "Reference to datetime object reqired\n";
183 56 100       166 $self->valid($date) ?
184             $self->_valid_decr($date) :
185             $self->_invalid_decr($date);
186             }
187              
188 47     47   916 sub _valid_incr { shift->_minute_incr(@_) }
189              
190 33     33   1329 sub _valid_decr { shift->_minute_decr(@_) }
191              
192             sub _invalid_incr {
193             # If provided date is valid, return it. Otherwise return
194             # nearest valid date after provided date.
195 57     57   797 my($self, $date) = @_;
196 57 50       180 ref $date or croak "Reference to datetime object reqired\n";
197              
198 57         147 print STDERR "\nI GOT: ", $date->datetime, "\n" if DEBUG;
199              
200 57 100       186 $date->truncate(to => 'minute')->add(minutes => 1)
201             if $date->second;
202              
203 57         8283 print STDERR "RND: ", $date->datetime, "\n" if DEBUG;
204              
205             # Find our greatest invalid unit and clip
206 57 100       192 if (!$self->month->contains($date->month)) {
    100          
    100          
207 5         78 $date->truncate(to => 'month');
208             }
209             elsif (!$self->days_contain($date->day, $date->dow)) {
210 34         169 $date->truncate(to => 'day');
211             }
212             elsif (!$self->hour->contains($date->hour)) {
213 5         80 $date->truncate(to => 'hour');
214             }
215             else {
216 13         198 $date->truncate(to => 'minute');
217             }
218              
219 57         22150 print STDERR "BBT: ", $date->datetime, "\n" if DEBUG;
220              
221 57 100       196 return $date if $self->valid($date);
222              
223 49         715 print STDERR "ZZT: ", $date->datetime, "\n" if DEBUG;
224              
225             # Extraneous durations clipped. Start searching.
226 49         152 while (!$self->valid($date)) {
227 74         1190 $date->add(months => 1) until $self->month->contains($date->month);
228 74         965 print STDERR "MON: ", $date->datetime, "\n" if DEBUG;
229              
230 74         260 my $day_orig = $date->day;
231 74         491 $date->add(days => 1) until $self->days_contain($date->day, $date->dow);
232 74 100 50     314 $date->truncate(to => 'month') && next if $date->day < $day_orig;
233 63         604 print STDERR "DAY: ", $date->datetime, "\n" if DEBUG;
234              
235 63         259 my $hour_orig = $date->hour;
236 63         485 $date->add(hours => 1) until $self->hour->contains($date->hour);
237 63 100 50     965 $date->truncate(to => 'day') && next if $date->hour < $hour_orig;
238 58         414 print STDERR "HOR: ", $date->datetime, "\n" if DEBUG;
239              
240 58         244 my $min_orig = $date->minute;
241 58         474 $date->add(minutes => 1) until $self->minute->contains($date->minute);
242 58 100 50     1234 $date->truncate(to => 'hour') && next if $date->minute < $min_orig;
243 53         564 print STDERR "MIN: ", $date->datetime, "\n" if DEBUG;
244             }
245 49         791 print STDERR "SET: ", $date->datetime, "\n" if DEBUG;
246 49         413 $date;
247             }
248              
249             sub _invalid_decr {
250             # If provided date is valid, return it. Otherwise
251             # return the nearest previous valid date.
252 47     47   1671 my($self, $date) = @_;
253 47 50       177 ref $date or croak "Reference to datetime object reqired\n";
254              
255 47         68 print STDERR "\nD GOT: ", $date->datetime, "\n" if DEBUG;
256              
257 47 100       167 if (!$self->month->contains($date->month)) {
    100          
    100          
258 3         47 $date->truncate(to => 'month');
259             }
260             elsif (!$self->days_contain($date->day, $date->dow)) {
261 30         204 $date->truncate(to => 'day');
262             }
263             elsif (!$self->hour->contains($date->hour)) {
264 3         56 $date->truncate(to => 'hour');
265             }
266             else {
267 11         210 $date->truncate(to => 'minute');
268             }
269              
270 47         40769 print STDERR "BBT: ", $date->datetime, "\n" if DEBUG;
271              
272 47 100       182 return $date if $self->valid($date);
273              
274 40         954 print STDERR "ZZT: ", $date->datetime, "\n" if DEBUG;
275              
276             # Extraneous durations clipped. Start searching.
277 40         122 while (!$self->valid($date)) {
278 69 100       1148 if (!$self->month->contains($date->month)) {
279 17         252 $date->subtract(months => 1) until $self->month->contains($date->month);
280 17         268 $self->_unit_peak($date, 'month');
281 17         40694 print STDERR "MON: ", $date->datetime, "\n" if DEBUG;
282             }
283 69 100       851 if (!$self->days_contain($date->day, $date->dow)) {
284 53         185 my $day_orig = $date->day;
285 53         501 $date->subtract(days => 1)
286             until $self->days_contain($date->day, $date->dow);
287 53 100 50     208 $self->_unit_peak($date, 'month') && next if ($date->day > $day_orig);
288 29         292 $self->_unit_peak($date, 'day');
289 29         57005 print STDERR "DAY: ", $date->datetime, "\n" if DEBUG;
290             }
291 45 100       397 if (!$self->hour->contains($date->hour)) {
292 41         677 my $hour_orig = $date->hour;
293 41         271 $date->subtract(hours => 1) until $self->hour->contains($date->hour);
294 41 100 50     687 $self->_unit_peak($date, 'day') && next if ($date->hour > $hour_orig);
295 38         349 $self->_unit_peak($date, 'hour');
296 38         93268 print STDERR "HOR: ", $date->datetime, "\n" if DEBUG;
297             }
298 42 50       373 if (!$self->minute->contains($date->minute)) {
299 42         724 my $min_orig = $date->minute;
300 42         260 $date->subtract(minutes => 1)
301             until $self->minute->contains($date->minute);
302 42 100 50     698 $self->_unit_peak($date, 'hour') && next if ($date->minute > $min_orig);
303 40         527 print STDERR "MIN: ", $date->datetime, "\n" if DEBUG;
304             }
305             }
306 40         1541 print STDERR "SET: ", $date->datetime, "\n" if DEBUG;
307 40         327 $date;
308             }
309              
310             ###
311              
312             sub _unit_peak {
313 113     113   498 my($self, $date, $unit) = @_;
314 113 50 33     453 $date && $unit or croak "DateTime ref and unit required.\n";
315 113         5510 $date->truncate(to => $unit)
316             ->add($unit . 's' => 1)
317             ->subtract(minutes => 1);
318             }
319              
320             ### Unit cascades
321              
322             sub _minute_incr {
323 47     47   92 my($self, $date) = @_;
324 47 50       143 croak "datetime object required\n" unless $date;
325 47         1959 my $cur = $date->minute;
326 47         287 my $next = $self->minute->next($cur);
327 47         297 $date->set(minute => $next);
328 47 100       21165 $next <= $cur ? $self->_hour_incr($date) : $date;
329             }
330              
331             sub _hour_incr {
332 34     34   76 my($self, $date) = @_;
333 34 50       115 croak "datetime object required\n" unless $date;
334 34         1370 my $cur = $date->hour;
335 34         244 my $next = $self->hour->next($cur);
336 34         135 $date->set(hour => $next);
337 34 100       13803 $next <= $cur ? $self->_day_incr($date) : $date;
338             }
339              
340             sub _day_incr {
341 30     30   79 my($self, $date) = @_;
342 30 50       113 croak "datetime object required\n" unless $date;
343 30         1330 $date->add(days => 1);
344 30         22974 $self->_invalid_incr($date);
345             }
346              
347             sub _minute_decr {
348 33     33   59 my($self, $date) = @_;
349 33 50       268 croak "datetime object required\n" unless $date;
350 33         1533 my $cur = $date->minute;
351 33         207 my $next = $self->minute->previous($cur);
352 33         172 $date->set(minute => $next);
353 33 100       26798 $next >= $cur ? $self->_hour_decr($date) : $date;
354             }
355              
356             sub _hour_decr {
357 30     30   543 my($self, $date) = @_;
358 30 50       113 croak "datetime object required\n" unless $date;
359 30         1409 my $cur = $date->hour;
360 30         336 my $next = $self->hour->previous($cur);
361 30         117 $date->set(hour => $next);
362 30 100       11106 $next >= $cur ? $self->_day_decr($date) : $date;
363             }
364              
365             sub _day_decr {
366 24     24   147 my($self, $date) = @_;
367 24 50       92 croak "datetime object required\n" unless $date;
368 24         1139 $date->subtract(days => 1);
369 24         24230 $self->_invalid_decr($date);
370             }
371              
372             ### Factories
373              
374 48     48   68 sub _make_cronset { shift; DateTime::Event::Cron::IntegratedSet->new(@_) }
  48         207  
375              
376             ### Shortcuts
377              
378 2410     2410 0 1751503 sub days_contain { shift->_cronset->days_contain(@_) }
379              
380 3362     3362 0 2664077 sub minute { shift->_cronset->minute }
381 1758     1758 0 1416503 sub hour { shift->_cronset->hour }
382 0     0 0 0 sub day { shift->_cronset->day }
383 858     858 0 295540 sub month { shift->_cronset->month }
384 0     0 0 0 sub dow { shift->_cronset->dow }
385 0     0 1 0 sub user { shift->_cronset->user }
386 0     0 1 0 sub command { shift->_cronset->command }
387 0     0 1 0 sub original { shift->_cronset->original }
388              
389             ### Static acessors/mutators
390              
391 8415     8415   23694 sub _cronset { shift->_attr('cronset', @_) }
392              
393             sub _attr {
394 8415     8415   12607 my $self = shift;
395 8415         17245 my $name = shift;
396 8415 100       21348 if (@_) {
397 27         136 $Object_Attributes{$self}{$name} = shift;
398             }
399 8415         71417 $Object_Attributes{$self}{$name};
400             }
401              
402             ### debugging
403              
404             sub _dump_sets {
405 0     0   0 my($self, $date) = @_;
406 0         0 foreach (qw(minute hour day month dow)) {
407 0         0 print STDERR "$_: ", join(',',$self->$_->list), "\n";
408             }
409 0 0       0 if (ref $date) {
410 0         0 $date = $date->clone;
411 0         0 my @mod;
412 0         0 my $mon = $date->month;
413 0         0 $date->truncate(to => 'month');
414 0         0 while ($date->month == $mon) {
415 0 0       0 push(@mod, $date->day) if $self->days_contain($date->day, $date->dow);
416 0         0 $date->add(days => 1);
417             }
418 0         0 print STDERR "mod for month($mon): ", join(',', @mod), "\n";
419             }
420 0         0 print STDERR "day_squelch: ", $self->_cronset->day_squelch, " ",
421             "dow_squelch: ", $self->_cronset->dow_squelch, "\n";
422 0         0 $self;
423             }
424              
425             ###
426              
427 48     48   15695 sub DESTROY { delete $Object_Attributes{shift()} }
428              
429             ##########
430              
431             {
432              
433             package DateTime::Event::Cron::IntegratedSet;
434              
435             # IntegratedSet manages the collection of field sets for
436             # each cron entry, including sanity checks. Individual
437             # field sets are accessed through their respective names,
438             # i.e., minute hour day month dow.
439             #
440             # Also implements some merged field logic for day/dow
441             # interactions.
442              
443 6     6   183 use strict;
  6         13  
  6         243  
444 6     6   32 use Carp;
  6         12  
  6         9934  
445              
446             my %Range = (
447             minute => [0..59],
448             hour => [0..23],
449             day => [1..31],
450             month => [1..12],
451             dow => [1..7],
452             );
453              
454             my @Month_Max = qw( 31 29 31 30 31 30 31 31 30 31 30 31 );
455              
456             my %Object_Attributes;
457              
458             sub new {
459 48     48   82 my $self = [];
460 48         177 bless $self, shift;
461 48         171 $self->_range(\%Range);
462 48         160 $self->set_cron(@_);
463 27         66 $self;
464             }
465              
466             sub set_cron {
467             # Initialize
468 48     48   82 my $self = shift;
469 48         117 my %parms = @_;
470 48         96 my $cron = $parms{cron};
471 48         70 my $user_mode = $parms{user_mode};
472 48 100       262 defined $cron or croak "Cron entry fields required\n";
473 47         141 $self->_attr('original', $cron);
474 47         64 my @line;
475 47 100       105 if (ref $cron) {
476 1         8 @line = grep(!/^\s*$/, @$cron);
477             }
478             else {
479 46         138 $cron =~ s/^\s+//;
480 46         165 $cron =~ s/\s+$//;
481 46         525 @line = split(/\s+/, $cron);
482             }
483 47 100       1483 @line >= 5 or croak "At least five cron entry fields required.\n";
484 38         405 my @entry = splice(@line, 0, 5);
485 38         53 my($user, $command);
486 38 50       106 unless (defined $user_mode) {
487             # auto-detect
488 38 100 66     227 if (@line > 1 && $line[0] =~ /^\w+$/) {
489 4         5 $user_mode = 1;
490             }
491             }
492 38 100       95 $user = shift @line if $user_mode;
493 38         1208 $command = join(' ', @line);
494 38         94 $self->_attr('command', $command);
495 38         87 $self->_attr('user', $user);
496 38         48 my $i = 0;
497 38         125 foreach my $name (qw( minute hour day month dow )) {
498 169         402 $self->_attr($name, $self->make_valid_set($name, $entry[$i]));
499 160         372 ++$i;
500             }
501 29         105 my @day_list = $self->day->list;
502 29         323 my @dow_list = $self->dow->list;
503 29         245 my $day_range = $self->range('day');
504 29         69 my $dow_range = $self->range('dow');
505 29 100 100     214 $self->day_squelch(scalar @day_list == scalar @$day_range &&
506             scalar @dow_list != scalar @$dow_range ? 1 : 0);
507 29 100 100     204 $self->dow_squelch(scalar @dow_list == scalar @$dow_range &&
508             scalar @day_list != scalar @$day_range ? 1 : 0);
509 29 100       91 unless ($self->day_squelch) {
510 23         60 my @days = $self->day->list;
511 23         239 my $pass = 0;
512 23         67 MONTH: foreach my $month ($self->month->list) {
513 27         181 foreach (@days) {
514 27 100 50     167 ++$pass && last MONTH if $_ <= $Month_Max[$month - 1];
515             }
516             }
517 23 100       614 croak "Impossible last day for provided months.\n" unless $pass;
518             }
519 27         177 $self;
520             }
521              
522             # Field range queries
523             sub range {
524 227     227   287 my($self, $name) = @_;
525 227 50       415 my $val = $self->_range->{$name} or croak "Unknown field '$name'\n";
526 227         402 $val;
527             }
528              
529             # Perform sanity checks when setting up each field set.
530             sub make_valid_set {
531 169     169   327 my($self, $name, $str) = @_;
532 169         399 my $range = $self->range($name);
533 169         361 my $set = $self->make_set($str, $range);
534 169         504 my @list = $set->list;
535 169 100       1889 croak "Malformed cron field '$str'\n" unless @list;
536 167 100       1248 croak "Field value ($list[-1]) out of range ($range->[0]-$range->[-1])\n"
537             if $list[-1] > $range->[-1];
538 162 100 100     519 if ($name eq 'dow' && $set->contains(0)) {
539 3         39 shift(@list);
540 3 50       10 push(@list, 7) unless $set->contains(7);
541 3         35 $set = $self->make_set(join(',',@list), $range);
542             }
543 162 100       878 croak "Field value ($list[0]) out of range ($range->[0]-$range->[-1])\n"
544             if $list[0] < $range->[0];
545 160         587 $set;
546             }
547              
548             # No sanity checks
549 172     172   181 sub make_set { shift; DateTime::Event::Cron::OrderedSet->new(@_) }
  172         466  
550              
551             # Flags for when day/dow are applied.
552 2468     2468   9724 sub day_squelch { shift->_attr('day_squelch', @_ ) }
553 2239     2239   4337 sub dow_squelch { shift->_attr('dow_squelch', @_ ) }
554              
555             # Merged logic for day/dow
556             sub days_contain {
557 2410     2410   5256 my($self, $day, $dow) = @_;
558 2410 50 33     15496 defined $day && defined $dow
559             or croak "Day of month and day of week required.\n";
560 2410         16960 my $day_c = $self->day->contains($day);
561 2410         20756 my $dow_c = $self->dow->contains($dow);
562 2410 100       31207 return $dow_c if $self->day_squelch;
563 2210 100       4825 return $day_c if $self->dow_squelch;
564 294 100       5682 $day_c || $dow_c;
565             }
566              
567             # Set Accessors
568 3362     3362   12394 sub minute { shift->_attr('minute' ) }
569 1758     1758   10413 sub hour { shift->_attr('hour' ) }
570 2462     2462   5157 sub day { shift->_attr('day' ) }
571 881     881   1862 sub month { shift->_attr('month' ) }
572 2439     2439   5470 sub dow { shift->_attr('dow' ) }
573 0     0   0 sub user { shift->_attr('user' ) }
574 0     0   0 sub command { shift->_attr('command') }
575 0     0   0 sub original { shift->_attr('original') }
576              
577             # Accessors/mutators
578 275     275   554 sub _range { shift->_attr('range', @_) }
579              
580             sub _attr {
581 16167     16167   25807 my $self = shift;
582 16167         29777 my $name = shift;
583 16167 100       45250 if (@_) {
584 389         1317 $Object_Attributes{$self}{$name} = shift;
585             }
586 16167         132023 $Object_Attributes{$self}{$name};
587             }
588              
589 48     48   281 sub DESTROY { delete $Object_Attributes{shift()} }
590              
591             }
592              
593             ##########
594              
595             {
596              
597             package DateTime::Event::Cron::OrderedSet;
598              
599             # Extends Set::Crontab with some progression logic (next/prev)
600              
601 6     6   61 use strict;
  6         32  
  6         206  
602 6     6   27 use Carp;
  6         12  
  6         467  
603 6     6   45 use base 'Set::Crontab';
  6         11  
  6         3476  
604              
605             my %Object_Attributes;
606              
607             sub new {
608 172     172   209 my $class = shift;
609 172         234 my($string, $range) = @_;
610 172 50 33     1095 defined $string && ref $range
611             or croak "Cron field and range ref required.\n";
612 172         637 my $self = Set::Crontab->new($string, $range);
613 172         21870 bless $self, $class;
614 172         510 my @list = $self->list;
615 172         1866 my(%next, %prev);
616 172         398 foreach (0 .. $#list) {
617 2038         4555 $next{$list[$_]} = $list[($_+1)%@list];
618 2038         4941 $prev{$list[$_]} = $list[($_-1)%@list];
619             }
620 172         491 $self->_attr('next', \%next);
621 172         384 $self->_attr('previous', \%prev);
622 172         825 $self;
623             }
624              
625             sub next {
626 81     81   215 my($self, $entry) = @_;
627 81         213 my $hash = $self->_attr('next');
628 81 50       299 croak "Missing entry($entry) in set\n" unless exists $hash->{$entry};
629 81         167 my $next = $hash->{$entry};
630 81 50       413 wantarray ? ($next, $next <= $entry) : $next;
631             }
632              
633             sub previous {
634 63     63   106 my($self, $entry) = @_;
635 63         166 my $hash = $self->_attr('previous');
636 63 50       211 croak "Missing entry($entry) in set\n" unless exists $hash->{$entry};
637 63         141 my $prev = $hash->{$entry};
638 63 50       220 wantarray ? ($prev, $prev >= $entry) : $prev;
639             }
640              
641             sub _attr {
642 488     488   683 my $self = shift;
643 488         564 my $name = shift;
644 488 100       988 if (@_) {
645 344         2594 $Object_Attributes{$self}{$name} = shift;
646             }
647 488         1557 $Object_Attributes{$self}{$name};
648             }
649              
650 172     172   3143 sub DESTROY { delete $Object_Attributes{shift()} }
651              
652             }
653              
654             ###
655              
656             1;
657              
658             __END__