line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# You may distribute under the terms of either the GNU General Public License |
2
|
|
|
|
|
|
|
# or the Artistic License (the same terms as Perl itself) |
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
# (C) Paul Evans, 2012-2014 -- leonerd@leonerd.org.uk |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package Algorithm::Cron; |
7
|
|
|
|
|
|
|
|
8
|
5
|
|
|
5
|
|
84819
|
use strict; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
197
|
|
9
|
5
|
|
|
5
|
|
19
|
use warnings; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
373
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
our $VERSION = '0.10'; |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
my @FIELDS = qw( sec min hour mday mon year wday ); |
14
|
|
|
|
|
|
|
my @FIELDS_CTOR = grep { $_ ne "year" } @FIELDS; |
15
|
|
|
|
|
|
|
|
16
|
5
|
|
|
5
|
|
62
|
use Carp; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
378
|
|
17
|
5
|
|
|
5
|
|
2288
|
use POSIX qw( mktime strftime setlocale LC_TIME ); |
|
5
|
|
|
|
|
22994
|
|
|
5
|
|
|
|
|
30
|
|
18
|
5
|
|
|
5
|
|
6409
|
use Time::timegm qw( timegm ); |
|
5
|
|
|
|
|
4574
|
|
|
5
|
|
|
|
|
3052
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 NAME |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
C - abstract implementation of the F scheduling |
23
|
|
|
|
|
|
|
algorithm |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 SYNOPSIS |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
use Algorithm::Cron; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
my $cron = Algorithm::Cron->new( |
30
|
|
|
|
|
|
|
base => 'local', |
31
|
|
|
|
|
|
|
crontab => "*/10 9-17 * * *", |
32
|
|
|
|
|
|
|
); |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
my $time = time; |
35
|
|
|
|
|
|
|
while(1) { |
36
|
|
|
|
|
|
|
$time = $cron->next_time( $time ); |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
sleep( time - $time ); |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
print "Do something\n"; |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
=head1 DESCRIPTION |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
Objects in this class implement a time scheduling algorithm such as used by |
46
|
|
|
|
|
|
|
F. Objects are stateless once constructed, and represent a single |
47
|
|
|
|
|
|
|
schedule as defined by a F entry. The object implements a method |
48
|
|
|
|
|
|
|
C which returns an epoch timestamp value to indicate the next time |
49
|
|
|
|
|
|
|
included in the crontab schedule. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head2 Crontabs |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
The schedule is provided as a set of acceptable values for each field of the |
54
|
|
|
|
|
|
|
broken-down time (as returned by C or C), either in a |
55
|
|
|
|
|
|
|
single string called C or by a set of named strings, each taking the |
56
|
|
|
|
|
|
|
name of a F field. |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
my $cron = Algorithm::Cron->new( |
59
|
|
|
|
|
|
|
base => 'local', |
60
|
|
|
|
|
|
|
crontab => '0 9 * * mon-fri', |
61
|
|
|
|
|
|
|
); |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
Z<> |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
my $cron = Algorithm::Cron->new( |
66
|
|
|
|
|
|
|
base => 'local', |
67
|
|
|
|
|
|
|
min => 0, |
68
|
|
|
|
|
|
|
hour => 9, |
69
|
|
|
|
|
|
|
wday => "mon-fri", |
70
|
|
|
|
|
|
|
); |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
A C field containing a single asterisk (C<*>), or a missing named |
73
|
|
|
|
|
|
|
field, indicates that any value here is included in the scheduled times. To |
74
|
|
|
|
|
|
|
restrict the schedule, a value or set of values can be provided. This should |
75
|
|
|
|
|
|
|
consist of one or more comma-separated numbers or ranges, where a range is |
76
|
|
|
|
|
|
|
given as the start and end points, both inclusive. |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
hour => "3-6" |
79
|
|
|
|
|
|
|
hour => "3,4,5,6" |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Ranges can also be prefixed by a value to give the increment for values in |
82
|
|
|
|
|
|
|
that range. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
min => "*/10" |
85
|
|
|
|
|
|
|
min => "0,10,20,30,40,50" |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
The C and C fields also allow symbolic month or weekday names in |
88
|
|
|
|
|
|
|
place of numeric values. These names are always in the C locale, regardless of |
89
|
|
|
|
|
|
|
the system's locale settings. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
mon => "mar-sep" |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
wday => "mon,wed,fri" |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
Specifying C as the end of a C range, or giving the numeric value |
96
|
|
|
|
|
|
|
of C<7> is also supported. |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
wday => "fri-sun" |
99
|
|
|
|
|
|
|
wday => "5-7" |
100
|
|
|
|
|
|
|
# Both equivalent to: wday => "0,5,6" |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
As per F behaviour, this algorithm looks for a match of the C, |
103
|
|
|
|
|
|
|
C and C fields, and at least one of the C or C fields. |
104
|
|
|
|
|
|
|
If both C and C are specified, a match of either will be |
105
|
|
|
|
|
|
|
sufficient. |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
As an extension, seconds may be provided either by passing six space-separated |
108
|
|
|
|
|
|
|
fields in the C string, or as an additional C field. If not |
109
|
|
|
|
|
|
|
provided it will default to C<0>. If six fields are provided, the first gives |
110
|
|
|
|
|
|
|
the seconds. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=head2 Time Base |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
C supports using either UTC or the local timezone when |
115
|
|
|
|
|
|
|
comparing against the given schedule. |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=cut |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
# mday field starts at 1, others start at 0 |
120
|
|
|
|
|
|
|
my %MIN = ( |
121
|
|
|
|
|
|
|
sec => 0, |
122
|
|
|
|
|
|
|
min => 0, |
123
|
|
|
|
|
|
|
hour => 0, |
124
|
|
|
|
|
|
|
mday => 1, |
125
|
|
|
|
|
|
|
mon => 0 |
126
|
|
|
|
|
|
|
); |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
# These don't have to be real maxima, as the algorithm will cope. These are |
129
|
|
|
|
|
|
|
# just the top end of the range expansions |
130
|
|
|
|
|
|
|
my %MAX = ( |
131
|
|
|
|
|
|
|
sec => 59, |
132
|
|
|
|
|
|
|
min => 59, |
133
|
|
|
|
|
|
|
hour => 23, |
134
|
|
|
|
|
|
|
mday => 31, |
135
|
|
|
|
|
|
|
mon => 11, |
136
|
|
|
|
|
|
|
wday => 6, |
137
|
|
|
|
|
|
|
); |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
my %MONTHS; |
140
|
|
|
|
|
|
|
my %WDAYS; |
141
|
|
|
|
|
|
|
# These always want to be in LC_TIME=C |
142
|
|
|
|
|
|
|
{ |
143
|
|
|
|
|
|
|
my $old_loc = setlocale( LC_TIME ); |
144
|
|
|
|
|
|
|
setlocale( LC_TIME, "C" ); |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
%MONTHS = map { lc(strftime "%b", 0,0,0, 1, $_, 70), $_ } 0 .. 11; |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# 0 = Sun. 4th Jan 1970 was a Sunday |
149
|
|
|
|
|
|
|
%WDAYS = map { lc(strftime "%a", 0,0,0, 4+$_, 0, 70), $_ } 0 .. 6; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
setlocale( LC_TIME, $old_loc ); |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub _expand_set |
155
|
|
|
|
|
|
|
{ |
156
|
136
|
|
|
136
|
|
170
|
my ( $spec, $kind ) = @_; |
157
|
|
|
|
|
|
|
|
158
|
136
|
100
|
|
|
|
302
|
return undef if $spec eq "*"; |
159
|
|
|
|
|
|
|
|
160
|
87
|
|
|
|
|
81
|
my @vals; |
161
|
87
|
|
|
|
|
182
|
foreach my $val ( split m/,/, $spec ) { |
162
|
88
|
|
|
|
|
84
|
my $step = 1; |
163
|
88
|
|
|
|
|
73
|
my $end; |
164
|
|
|
|
|
|
|
|
165
|
88
|
100
|
|
|
|
207
|
$val =~ s{/(\d+)$}{} and $step = $1; |
166
|
|
|
|
|
|
|
|
167
|
88
|
100
|
|
|
|
189
|
$val =~ m{^(.+)-(.+)$} and ( $val, $end ) = ( $1, $2 ); |
168
|
88
|
100
|
|
|
|
331
|
if( $val eq "*" ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
169
|
7
|
|
|
|
|
53
|
( $val, $end ) = ( $MIN{$kind}, $MAX{$kind} ); |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
elsif( $kind eq "mon" ) { |
172
|
|
|
|
|
|
|
# Users specify 1-12 but we want 0-11 |
173
|
10
|
|
100
|
|
|
137
|
defined and m/^\d+$/ and $_-- for $val, $end; |
|
|
|
66
|
|
|
|
|
174
|
|
|
|
|
|
|
# Convert symbolics |
175
|
10
|
|
100
|
|
|
97
|
defined and exists $MONTHS{lc $_} and $_ = $MONTHS{lc $_} for $val, $end; |
|
|
|
66
|
|
|
|
|
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
elsif( $kind eq "wday" ) { |
178
|
|
|
|
|
|
|
# Convert symbolics |
179
|
11
|
|
100
|
|
|
110
|
defined and exists $WDAYS{lc $_} and $_ = $WDAYS{lc $_} for $val, $end; |
|
|
|
66
|
|
|
|
|
180
|
11
|
100
|
100
|
|
|
60
|
$end = 7 if defined $end and $end == 0 and $val > 0; |
|
|
|
66
|
|
|
|
|
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
88
|
100
|
|
|
|
432
|
$val =~ m/^\d+$/ or croak "$val is unrecognised for $kind"; |
184
|
87
|
100
|
33
|
|
|
179
|
$end =~ m/^\d+$/ or croak "$end is unrecognised for $kind" if defined $end; |
185
|
|
|
|
|
|
|
|
186
|
87
|
|
|
|
|
118
|
push @vals, $val; |
187
|
87
|
|
100
|
|
|
337
|
push @vals, $val while defined $end and ( $val += $step ) <= $end; |
188
|
|
|
|
|
|
|
|
189
|
87
|
100
|
100
|
|
|
335
|
if( $kind eq "wday" && $vals[-1] == 7 ) { |
190
|
2
|
50
|
|
|
|
12
|
unshift @vals, 0 unless $vals[0] == 0; |
191
|
2
|
|
|
|
|
4
|
pop @vals; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
86
|
|
|
|
|
287
|
return \@vals; |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
5
|
|
|
5
|
|
32
|
use constant { EXTRACT => 0, BUILD => 1, NORMALISE => 2 }; |
|
5
|
|
|
|
|
6
|
|
|
5
|
|
|
|
|
1003
|
|
199
|
|
|
|
|
|
|
my %time_funcs = ( |
200
|
|
|
|
|
|
|
# EXTRACT BUILD NORMALISE |
201
|
|
|
|
|
|
|
local => [ sub { localtime $_[0] }, \&mktime, sub { localtime mktime @_[0..5], -1, -1, -1 } ], |
202
|
|
|
|
|
|
|
utc => [ sub { gmtime $_[0] }, \&timegm, sub { gmtime timegm @_[0..5], -1, -1, -1 } ], |
203
|
|
|
|
|
|
|
); |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
# Indices in time array |
206
|
|
|
|
|
|
|
use constant { |
207
|
5
|
|
|
|
|
1933
|
TM_SEC => 0, |
208
|
|
|
|
|
|
|
TM_MIN => 1, |
209
|
|
|
|
|
|
|
TM_HOUR => 2, |
210
|
|
|
|
|
|
|
TM_MDAY => 3, |
211
|
|
|
|
|
|
|
TM_MON => 4, |
212
|
|
|
|
|
|
|
TM_YEAR => 5, |
213
|
|
|
|
|
|
|
TM_WDAY => 6, |
214
|
5
|
|
|
5
|
|
25
|
}; |
|
5
|
|
|
|
|
9
|
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=cut |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=head2 $cron = Algorithm::Cron->new( %args ) |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
Constructs a new C object representing the given schedule |
223
|
|
|
|
|
|
|
relative to the given time base. Takes the following named arguments: |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
=over 8 |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
=item base => STRING |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
Gives the time base used for scheduling. Either C or C. |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=item crontab => STRING |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
Gives the crontab schedule in 5 or 6 space-separated fields. |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=item sec => STRING, min => STRING, ... mon => STRING |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
Optional. Gives the schedule in a set of individual fields, if the C |
238
|
|
|
|
|
|
|
field is not specified. |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=back |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=cut |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
sub new |
245
|
|
|
|
|
|
|
{ |
246
|
22
|
|
|
22
|
1
|
18710
|
my $class = shift; |
247
|
22
|
|
|
|
|
158
|
my %params = @_; |
248
|
|
|
|
|
|
|
|
249
|
22
|
|
|
|
|
58
|
my $base = delete $params{base}; |
250
|
22
|
50
|
|
|
|
49
|
grep { $_ eq $base } qw( local utc ) or croak "Unrecognised base - should be 'local' or 'utc'"; |
|
44
|
|
|
|
|
176
|
|
251
|
|
|
|
|
|
|
|
252
|
22
|
100
|
|
|
|
118
|
if( exists $params{crontab} ) { |
253
|
21
|
|
|
|
|
42
|
my $crontab = delete $params{crontab}; |
254
|
21
|
|
|
|
|
188
|
s/^\s+//, s/\s+$// for $crontab; |
255
|
|
|
|
|
|
|
|
256
|
21
|
|
|
|
|
90
|
my @fields = split m/\s+/, $crontab; |
257
|
21
|
100
|
|
|
|
304
|
@fields >= 5 or croak "Expected at least 5 crontab fields"; |
258
|
20
|
50
|
|
|
|
131
|
@fields <= 6 or croak "Expected no more than 6 crontab fields"; |
259
|
|
|
|
|
|
|
|
260
|
20
|
100
|
|
|
|
143
|
@fields = ( "0", @fields ) if @fields < 6; |
261
|
20
|
|
|
|
|
235
|
@params{ @FIELDS_CTOR } = @fields; |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
21
|
100
|
|
|
|
95
|
$params{sec} = 0 unless exists $params{sec}; |
265
|
|
|
|
|
|
|
|
266
|
21
|
|
|
|
|
131
|
my $self = bless { |
267
|
|
|
|
|
|
|
base => $base, |
268
|
|
|
|
|
|
|
}, $class; |
269
|
|
|
|
|
|
|
|
270
|
21
|
|
|
|
|
183
|
foreach ( @FIELDS_CTOR ) { |
271
|
122
|
100
|
|
|
|
216
|
next unless exists $params{$_}; |
272
|
|
|
|
|
|
|
|
273
|
121
|
|
|
|
|
252
|
$self->{$_} = _expand_set( delete $params{$_}, $_ ); |
274
|
120
|
50
|
100
|
|
|
337
|
!defined $self->{$_} or scalar @{ $self->{$_} } or |
|
72
|
|
|
|
|
171
|
|
275
|
|
|
|
|
|
|
croak "Require at least one value for '$_' field"; |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
20
|
|
|
|
|
60
|
return $self; |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=head1 METHODS |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=cut |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
=head2 @seconds = $cron->sec |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head2 @minutes = $cron->min |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=head2 @hours = $cron->hour |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
=head2 @mdays = $cron->mday |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=head2 @months = $cron->mon |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=head2 @wdays = $cron->wday |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
Accessors that return a list of the accepted values for each scheduling field. |
298
|
|
|
|
|
|
|
These are returned in a plain list of numbers, regardless of the form they |
299
|
|
|
|
|
|
|
were specified to the constructor. |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
Also note that the list of valid months will be 0-based (in the range 0 to 11) |
302
|
|
|
|
|
|
|
rather than 1-based, to match the values used by C, C, |
303
|
|
|
|
|
|
|
C and C. |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=cut |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
foreach my $field ( @FIELDS_CTOR ) { |
308
|
5
|
|
|
5
|
|
32
|
no strict 'refs'; |
|
5
|
|
|
|
|
6
|
|
|
5
|
|
|
|
|
4930
|
|
309
|
|
|
|
|
|
|
*$field = sub { |
310
|
27
|
|
|
27
|
|
65
|
my $self = shift; |
311
|
27
|
100
|
|
|
|
24
|
@{ $self->{$field} || [] }; |
|
27
|
|
|
|
|
230
|
|
312
|
|
|
|
|
|
|
}; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub next_time_field |
316
|
|
|
|
|
|
|
{ |
317
|
978
|
|
|
978
|
0
|
901
|
my $self = shift; |
318
|
978
|
|
|
|
|
1015
|
my ( $t, $idx ) = @_; |
319
|
|
|
|
|
|
|
|
320
|
978
|
|
|
|
|
1204
|
my $funcs = $time_funcs{$self->{base}}; |
321
|
|
|
|
|
|
|
|
322
|
978
|
100
|
|
|
|
2562
|
my $spec = $self->{ $FIELDS[$idx] } or return 1; |
323
|
|
|
|
|
|
|
|
324
|
713
|
|
|
|
|
691
|
my $old = $t->[$idx]; |
325
|
713
|
|
|
|
|
599
|
my $new; |
326
|
|
|
|
|
|
|
|
327
|
713
|
|
66
|
|
|
2001
|
$_ >= $old and $new = $_, last for @$spec; |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
# wday field is special. We can't alter it directly; any changes to it have |
330
|
|
|
|
|
|
|
# to happen via mday |
331
|
713
|
100
|
|
|
|
1020
|
if( $idx == TM_WDAY ) { |
332
|
103
|
|
|
|
|
85
|
$idx = TM_MDAY; |
333
|
|
|
|
|
|
|
# Adjust $new by the same delta |
334
|
103
|
100
|
|
|
|
204
|
$new = $t->[TM_MDAY] + $new - $old if defined $new; |
335
|
103
|
|
|
|
|
126
|
$old = $t->[TM_MDAY]; |
336
|
|
|
|
|
|
|
|
337
|
103
|
100
|
|
|
|
286
|
if( !defined $new ) { |
|
|
100
|
|
|
|
|
|
338
|
|
|
|
|
|
|
# Next week |
339
|
16
|
|
|
|
|
52
|
$t->[$_] = $MIN{$FIELDS[$_]} for TM_SEC .. TM_HOUR; |
340
|
|
|
|
|
|
|
# Add more days, such that we hit the next occurance of $spec->[0] |
341
|
16
|
|
|
|
|
60
|
$t->[TM_MDAY] += $spec->[0] + 7 - $t->[TM_WDAY]; |
342
|
|
|
|
|
|
|
|
343
|
16
|
|
|
|
|
27
|
@$t = $funcs->[NORMALISE]->( @$t ); |
344
|
|
|
|
|
|
|
|
345
|
16
|
|
|
|
|
195
|
return 0; |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
elsif( $new > $old ) { |
348
|
8
|
|
|
|
|
38
|
$t->[$_] = $MIN{$FIELDS[$_]} for TM_SEC .. $idx-1; |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
else { |
352
|
610
|
100
|
|
|
|
1322
|
if( !defined $new ) { |
|
|
100
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# Rollover |
354
|
164
|
|
|
|
|
513
|
$t->[$_] = $MIN{$FIELDS[$_]} for TM_SEC .. $idx-1; |
355
|
164
|
|
|
|
|
252
|
$t->[$idx] = $spec->[0]; |
356
|
164
|
|
|
|
|
211
|
$t->[$idx+1]++; |
357
|
|
|
|
|
|
|
|
358
|
164
|
|
|
|
|
268
|
@$t = $funcs->[NORMALISE]->( @$t ); |
359
|
|
|
|
|
|
|
|
360
|
164
|
|
|
|
|
845
|
return 0; |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
elsif( $new > $old ) { |
363
|
|
|
|
|
|
|
# Next field; reset |
364
|
70
|
|
|
|
|
237
|
$t->[$_] = $MIN{$FIELDS[$_]} for TM_SEC .. $idx-1; |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
|
368
|
533
|
|
|
|
|
581
|
$t->[$idx] = $new; |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
# Detect rollover of month and reset to next month |
371
|
533
|
|
|
|
|
593
|
my $was_mon = $t->[TM_MON]; |
372
|
|
|
|
|
|
|
|
373
|
533
|
|
|
|
|
937
|
@$t = $funcs->[NORMALISE]->( @$t ); |
374
|
|
|
|
|
|
|
|
375
|
533
|
100
|
100
|
|
|
1609
|
if( $idx == TM_MDAY and $was_mon != $t->[TM_MON] ) { |
376
|
6
|
|
|
|
|
16
|
$t->[$_] = 0 for TM_SEC .. TM_HOUR; |
377
|
6
|
|
|
|
|
22
|
$t->[TM_MDAY] = 1; |
378
|
|
|
|
|
|
|
|
379
|
6
|
|
|
|
|
10
|
@$t = $funcs->[NORMALISE]->( @$t ); |
380
|
|
|
|
|
|
|
|
381
|
6
|
|
|
|
|
70
|
return 0; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
527
|
|
|
|
|
1307
|
return 1; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=head2 $time = $cron->next_time( $start_time ) |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
Returns the next scheduled time, as an epoch timestamp, after the given |
390
|
|
|
|
|
|
|
timestamp. This is a stateless operation; it does not change any state stored |
391
|
|
|
|
|
|
|
by the C<$cron> object. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=cut |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
sub next_time |
396
|
|
|
|
|
|
|
{ |
397
|
74
|
|
|
74
|
1
|
2388
|
my $self = shift; |
398
|
74
|
|
|
|
|
95
|
my ( $time ) = @_; |
399
|
|
|
|
|
|
|
|
400
|
74
|
|
|
|
|
193
|
my $funcs = $time_funcs{$self->{base}}; |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# Always need to add at least 1 second |
403
|
74
|
|
|
|
|
203
|
my @t = $funcs->[EXTRACT]->( $time + 1 ); |
404
|
|
|
|
|
|
|
|
405
|
245
|
100
|
|
|
|
567
|
RESTART: |
406
|
|
|
|
|
|
|
$self->next_time_field( \@t, TM_MON ) or goto RESTART; |
407
|
|
|
|
|
|
|
|
408
|
240
|
100
|
100
|
|
|
1080
|
if( defined $self->{mday} and defined $self->{wday} ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
409
|
|
|
|
|
|
|
# Now it gets tricky because cron allows a match of -either- mday or wday |
410
|
|
|
|
|
|
|
# rather than requiring both. So we'll work out which of the two is sooner |
411
|
29
|
|
|
|
|
29
|
my $next_time_by_wday; |
412
|
29
|
|
|
|
|
61
|
my @wday_t = @t; |
413
|
29
|
|
|
|
|
30
|
my $wday_restart = 0; |
414
|
29
|
100
|
|
|
|
55
|
$self->next_time_field( \@wday_t, TM_WDAY ) or $wday_restart = 1; |
415
|
29
|
|
|
|
|
90
|
$next_time_by_wday = $funcs->[BUILD]->( @wday_t ); |
416
|
|
|
|
|
|
|
|
417
|
29
|
|
|
|
|
28
|
my $next_time_by_mday; |
418
|
29
|
|
|
|
|
57
|
my @mday_t = @t; |
419
|
29
|
|
|
|
|
29
|
my $mday_restart = 0; |
420
|
29
|
100
|
|
|
|
100
|
$self->next_time_field( \@mday_t, TM_MDAY ) or $mday_restart = 1; |
421
|
29
|
|
|
|
|
78
|
$next_time_by_mday = $funcs->[BUILD]->( @mday_t ); |
422
|
|
|
|
|
|
|
|
423
|
29
|
100
|
|
|
|
49
|
if( $next_time_by_wday > $next_time_by_mday ) { |
424
|
4
|
|
|
|
|
11
|
@t = @mday_t; |
425
|
4
|
50
|
|
|
|
13
|
goto RESTART if $mday_restart; |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
else { |
428
|
25
|
|
|
|
|
62
|
@t = @wday_t; |
429
|
25
|
100
|
|
|
|
315
|
goto RESTART if $wday_restart; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
elsif( defined $self->{mday} ) { |
433
|
84
|
100
|
|
|
|
165
|
$self->next_time_field( \@t, TM_MDAY ) or goto RESTART; |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
elsif( defined $self->{wday} ) { |
436
|
74
|
100
|
|
|
|
97
|
$self->next_time_field( \@t, TM_WDAY ) or goto RESTART; |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
|
439
|
215
|
|
|
|
|
287
|
foreach my $idx ( TM_HOUR, TM_MIN, TM_SEC ) { |
440
|
517
|
100
|
|
|
|
800
|
$self->next_time_field( \@t, $idx ) or goto RESTART; |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
|
443
|
74
|
|
|
|
|
498
|
return $funcs->[BUILD]->( @t ); |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=head1 AUTHOR |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
Paul Evans |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
=cut |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
0x55AA; |