Algorithm-Cron
view release on metacpan or search on metacpan
[BUGFIXES]
* Fix t/90rt84352.t that fails in some timezones (RT103111)
0.09 2014/05/14 17:27:49
[CHANGES]
* Sanity-check time fields in the constructor and reject attempts to
specify a list of no values for any field
[BUGFIXES]
* Space-trim crontab string before parsing, so leading space doesn't
upset the seconds field (RT95454)
0.08 2013/11/06 18:07:40
[BUGFIXES]
* Declare dependency on perl >= 5.008 for 'use constant'
* Perl 5.18 ignores 'wday' field to POSIX::strftime(); need to use
mday adjustments for generating %WDAY hash (RT89947)
0.07 BUGFIXES:
* Need to pass explicit -1, -1, -1 to mktime() on some platforms to
override tm_isdst detection (RT84352)
wday => "fri-sun"
wday => "5-7"
# Both equivalent to: wday => "0,5,6"
As per cron(8) behaviour, this algorithm looks for a match of the `min',
`hour' and `mon' fields, and at least one of the `mday' or `mday'
fields. If both `mday' and `wday' are specified, a match of either will
be sufficient.
As an extension, seconds may be provided either by passing six
space-separated fields in the `crontab' string, or as an additional
`sec' field. If not provided it will default to `0'. If six fields are
provided, the first gives the seconds.
Time Base
`Algorithm::Cron' supports using either UTC or the local timezone when
comparing against the given schedule.
CONSTRUCTOR
$cron = Algorithm::Cron->new( %args )
Constructs a new `Algorithm::Cron' object representing the given
schedule relative to the given time base. Takes the following named
arguments:
base => STRING
Gives the time base used for scheduling. Either `utc' or
`local'.
crontab => STRING
Gives the crontab schedule in 5 or 6 space-separated fields.
sec => STRING, min => STRING, ... mon => STRING
Optional. Gives the schedule in a set of individual fields, if
the `crontab' field is not specified.
METHODS
@seconds = $cron->sec
@minutes = $cron->min
@hours = $cron->hour
@mdays = $cron->mday
@months = $cron->mon
@wdays = $cron->wday
Accessors that return a list of the accepted values for each scheduling
field. These are returned in a plain list of numbers, regardless of the
form they were specified to the constructor.
Also note that the list of valid months will be 0-based (in the range 0
lib/Algorithm/Cron.pm view on Meta::CPAN
#
# (C) Paul Evans, 2012-2014 -- leonerd@leonerd.org.uk
package Algorithm::Cron;
use strict;
use warnings;
our $VERSION = '0.10';
my @FIELDS = qw( sec min hour mday mon year wday );
my @FIELDS_CTOR = grep { $_ ne "year" } @FIELDS;
use Carp;
use POSIX qw( mktime strftime setlocale LC_TIME );
use Time::timegm qw( timegm );
=head1 NAME
C<Algorithm::Cron> - abstract implementation of the F<cron(8)> scheduling
algorithm
lib/Algorithm/Cron.pm view on Meta::CPAN
wday => "fri-sun"
wday => "5-7"
# Both equivalent to: wday => "0,5,6"
As per F<cron(8)> behaviour, this algorithm looks for a match of the C<min>,
C<hour> and C<mon> fields, and at least one of the C<mday> or C<mday> fields.
If both C<mday> and C<wday> are specified, a match of either will be
sufficient.
As an extension, seconds may be provided either by passing six space-separated
fields in the C<crontab> string, or as an additional C<sec> field. If not
provided it will default to C<0>. If six fields are provided, the first gives
the seconds.
=head2 Time Base
C<Algorithm::Cron> supports using either UTC or the local timezone when
comparing against the given schedule.
=cut
# mday field starts at 1, others start at 0
my %MIN = (
sec => 0,
min => 0,
hour => 0,
mday => 1,
mon => 0
);
# These don't have to be real maxima, as the algorithm will cope. These are
# just the top end of the range expansions
my %MAX = (
sec => 59,
min => 59,
hour => 23,
mday => 31,
mon => 11,
wday => 6,
);
my %MONTHS;
my %WDAYS;
# These always want to be in LC_TIME=C
lib/Algorithm/Cron.pm view on Meta::CPAN
=over 8
=item base => STRING
Gives the time base used for scheduling. Either C<utc> or C<local>.
=item crontab => STRING
Gives the crontab schedule in 5 or 6 space-separated fields.
=item sec => STRING, min => STRING, ... mon => STRING
Optional. Gives the schedule in a set of individual fields, if the C<crontab>
field is not specified.
=back
=cut
sub new
{
lib/Algorithm/Cron.pm view on Meta::CPAN
s/^\s+//, s/\s+$// for $crontab;
my @fields = split m/\s+/, $crontab;
@fields >= 5 or croak "Expected at least 5 crontab fields";
@fields <= 6 or croak "Expected no more than 6 crontab fields";
@fields = ( "0", @fields ) if @fields < 6;
@params{ @FIELDS_CTOR } = @fields;
}
$params{sec} = 0 unless exists $params{sec};
my $self = bless {
base => $base,
}, $class;
foreach ( @FIELDS_CTOR ) {
next unless exists $params{$_};
$self->{$_} = _expand_set( delete $params{$_}, $_ );
!defined $self->{$_} or scalar @{ $self->{$_} } or
croak "Require at least one value for '$_' field";
}
return $self;
}
=head1 METHODS
=cut
=head2 @seconds = $cron->sec
=head2 @minutes = $cron->min
=head2 @hours = $cron->hour
=head2 @mdays = $cron->mday
=head2 @months = $cron->mon
=head2 @wdays = $cron->wday
lib/Algorithm/Cron.pm view on Meta::CPAN
=cut
sub next_time
{
my $self = shift;
my ( $time ) = @_;
my $funcs = $time_funcs{$self->{base}};
# Always need to add at least 1 second
my @t = $funcs->[EXTRACT]->( $time + 1 );
RESTART:
$self->next_time_field( \@t, TM_MON ) or goto RESTART;
if( defined $self->{mday} and defined $self->{wday} ) {
# Now it gets tricky because cron allows a match of -either- mday or wday
# rather than requiring both. So we'll work out which of the two is sooner
my $next_time_by_wday;
my @wday_t = @t;
t/01expand.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use Algorithm::Cron;
local *expand = \&Algorithm::Cron::_expand_set;
is_deeply( expand( "*", "sec" ), undef, 'expand sec=*' );
is_deeply( expand( "0", "sec" ), [ 0 ], 'expand sec=0' );
is_deeply( expand( "*/10", "sec" ), [ 0, 10, 20, 30, 40, 50 ], 'expand sec=0/10' );
is_deeply( expand( "5-8", "sec" ), [ 5, 6, 7, 8 ], 'expand sec=5-8' );
is_deeply( expand( "3-17/4", "sec" ), [ 3, 7, 11, 15 ], 'expand sec=3-17/4' );
is_deeply( expand( "*/5", "mday" ), [ 1, 6, 11, 16, 21, 26, 31 ], 'expand mday=*/5' );
is_deeply( expand( "jan", "mon" ), [ 0 ], 'expand mon=jan' );
is_deeply( expand( "mar-sep", "mon" ), [ 2 .. 8 ], 'expand mon=mar-sep' );
is_deeply( expand( "5", "mon" ), [ 4 ], 'expand mon=5' );
is_deeply( expand( "*/3", "mon" ), [ 0, 3, 6, 9 ], 'expand mon=*/3' );
is_deeply( expand( "mon", "wday" ), [ 1 ], 'expand wday=mon' );
is_deeply( expand( "mon-fri", "wday" ), [ 1 .. 5 ], 'expand wday=mon-fri' );
use Test::Fatal qw( dies_ok );
use Algorithm::Cron;
{
my $cron = Algorithm::Cron->new(
base => 'utc',
crontab => '0 0 0 1 0',
);
is_deeply( [ $cron->sec ], [ 0 ], '$cron->sec for 0 0 0 1 0' );
is_deeply( [ $cron->min ], [ 0 ], '$cron->min for 0 0 0 1 0' );
is_deeply( [ $cron->hour ], [ 0 ], '$cron->hour for 0 0 0 1 0' );
is_deeply( [ $cron->mday ], [ 0 ], '$cron->mday for 0 0 0 1 0' );
is_deeply( [ $cron->mon ], [ 0 ], '$cron->mon for 0 0 0 1 0' );
is_deeply( [ $cron->wday ], [ 0 ], '$cron->wday for 0 0 0 1 0' );
}
{
my $cron = Algorithm::Cron->new(
base => 'utc',
crontab => '1 3 5 7 9',
);
is_deeply( [ $cron->sec ], [ 0 ], '$cron->sec for 1 3 5 7 9' );
is_deeply( [ $cron->min ], [ 1 ], '$cron->min for 1 3 5 7 9' );
is_deeply( [ $cron->hour ], [ 3 ], '$cron->hour for 1 3 5 7 9' );
is_deeply( [ $cron->mday ], [ 5 ], '$cron->mday for 1 3 5 7 9' );
is_deeply( [ $cron->mon ], [ 6 ], '$cron->mon for 1 3 5 7 9' );
is_deeply( [ $cron->wday ], [ 9 ], '$cron->wday for 1 3 5 7 9' );
}
{
my $cron = Algorithm::Cron->new(
base => 'utc',
crontab => '* * * * *',
);
is_deeply( [ $cron->sec ], [ 0 ], '$cron->sec for * * * * *' );
is_deeply( [ $cron->min ], [], '$cron->min for * * * * *' );
is_deeply( [ $cron->hour ], [], '$cron->hour for * * * * *' );
is_deeply( [ $cron->mday ], [], '$cron->mday for * * * * *' );
is_deeply( [ $cron->mon ], [], '$cron->mon for * * * * *' );
is_deeply( [ $cron->wday ], [], '$cron->wday for * * * * *' );
}
{
my $cron = Algorithm::Cron->new(
base => 'utc',
crontab => '*/10 * * * * *',
);
is_deeply( [ $cron->sec ], [ 0, 10, 20, 30, 40, 50 ], '$cron->sec for */10 * * * * *' );
}
{
my $cron = Algorithm::Cron->new(
base => 'utc',
min => 10,
hour => 3,
mday => 15,
mon => 2,
);
is_deeply( [ $cron->sec ], [ 0 ], '$cron->sec for named' );
is_deeply( [ $cron->min ], [ 10 ], '$cron->min for named' );
is_deeply( [ $cron->hour ], [ 3 ], '$cron->hour for named' );
is_deeply( [ $cron->mday ], [ 15 ], '$cron->mday for named' );
is_deeply( [ $cron->mon ], [ 1 ], '$cron->mon for named' );
is_deeply( [ $cron->wday ], [], '$cron->wday for named' );
}
dies_ok { Algorithm::Cron->new( crontab => '@hourly', base => 'utc' ) }
"crontab => '\@hourly' dies";
'Unrecognised number dies';
# RT95454
{
my $cron = Algorithm::Cron->new(
base => 'utc',
crontab => ' 20 23 1 1 *'
);
is_deeply( [ $cron->min ], [ 20 ], '$cron->min for leading space' );
is_deeply( [ $cron->sec ], [ 0 ], '$cron->sec for leading space' );
my $next = $cron->next_time( 0 );
is( $next, 84000, '->next_time for crontab with space' );
}
done_testing;
t/03next_time.t view on Meta::CPAN
}
return @times;
}
is_deeply( [ list_times "* * * * * *", 5 ],
[ "2012-01-01 00:00:01",
"2012-01-01 00:00:02",
"2012-01-01 00:00:03",
"2012-01-01 00:00:04",
"2012-01-01 00:00:05" ], 'per second' );
is_deeply( [ list_times "* * * * *", 5 ],
[ "2012-01-01 00:01:00",
"2012-01-01 00:02:00",
"2012-01-01 00:03:00",
"2012-01-01 00:04:00",
"2012-01-01 00:05:00" ], 'per minute' );
is_deeply( [ list_times "*/10 * * * *", 7 ],
[ "2012-01-01 00:10:00",
t/03next_time.t view on Meta::CPAN
"2012-01-10 02:15:00",
"2012-01-15 02:15:00",
"2012-01-17 02:15:00",
"2012-01-24 02:15:00",
"2012-01-31 02:15:00",
"2012-02-07 02:15:00" ], '02:15 15th or Tuesday' );
is_deeply( [ list_times "59 59 23 31 01,03 *", 3 ],
[ "2012-01-31 23:59:59",
"2012-03-31 23:59:59",
"2013-01-31 23:59:59" ], 'last second of the month');
is_deeply( [ list_times "00 00 00 31 * *", 7 ],
[ "2012-01-31 00:00:00",
"2012-03-31 00:00:00",
"2012-05-31 00:00:00",
"2012-07-31 00:00:00",
"2012-08-31 00:00:00",
"2012-10-31 00:00:00",
"2012-12-31 00:00:00" ], '31st of each month');
( run in 0.776 second using v1.01-cache-2.11-cpan-39bf76dae61 )