Subject: | year crossings, time zones, pig and elephant DNA just don't splice |
When I run the attached test case, I get the following output. The test
case is used to parse a crontab entry and retrieve the next DateTime
according to that crontab entry.
It considers 'now' as March 30, 2007 and April 2, 2007 (see below), and
generates the following output:
0:> crontestcase.pl
Script start is 2007-03-30T19:21:08
Cron spec (15 15 5 1 *) ok, startlater calc'd as (2008-01-05T15:15:00)
Cron spec (15 15 10 1 *) ok, startlater calc'd as (2008-01-10T15:15:00)
Cron spec (15 15 5 2 *) ok, startlater calc'd as (2008-02-05T15:15:00)
Cron spec (15 15 10 2 *) ok, startlater calc'd as (2008-02-10T15:15:00)
Note it times out during calculating March:
Cron (15 15 5 3 *) calculation timed out, retrying with UTC
Cron spec (15 15 5 3 *) ok, startlater calc'd as (2008-03-05T15:15:00)
Cron (15 15 10 3 *) calculation timed out, retrying with UTC
Cron spec (15 15 10 3 *) ok, startlater calc'd as (2008-03-10T15:15:00)
But the rest is ok.
Cron spec (15 15 5 4 *) ok, startlater calc'd as (2007-04-05T15:15:00)
Cron spec (15 15 10 4 *) ok, startlater calc'd as (2007-04-10T15:15:00)
Cron spec (15 15 5 5 *) ok, startlater calc'd as (2007-05-05T15:15:00)
Cron spec (15 15 10 5 *) ok, startlater calc'd as (2007-05-10T15:15:00)
Cron spec (15 15 5 6 *) ok, startlater calc'd as (2007-06-05T15:15:00)
Cron spec (15 15 10 6 *) ok, startlater calc'd as (2007-06-10T15:15:00)
Cron spec (15 15 5 7 *) ok, startlater calc'd as (2007-07-05T15:15:00)
Cron spec (15 15 10 7 *) ok, startlater calc'd as (2007-07-10T15:15:00)
Cron spec (15 15 5 8 *) ok, startlater calc'd as (2007-08-05T15:15:00)
Cron spec (15 15 10 8 *) ok, startlater calc'd as (2007-08-10T15:15:00)
Cron spec (15 15 5 9 *) ok, startlater calc'd as (2007-09-05T15:15:00)
Cron spec (15 15 10 9 *) ok, startlater calc'd as (2007-09-10T15:15:00)
Cron spec (15 15 5 10 *) ok, startlater calc'd as (2007-10-05T15:15:00)
Cron spec (15 15 10 10 *) ok, startlater calc'd as (2007-10-10T15:15:00)
Cron spec (15 15 5 11 *) ok, startlater calc'd as (2007-11-05T15:15:00)
Cron spec (15 15 10 11 *) ok, startlater calc'd as (2007-11-10T15:15:00)
Cron spec (15 15 5 12 *) ok, startlater calc'd as (2007-12-05T15:15:00)
Cron spec (15 15 10 12 *) ok, startlater calc'd as (2007-12-10T15:15:00)
Now try April 2, 2007:
Script start is 2007-04-02T19:21:08
Mistakenly reporting 2009 instead of 2008, and for only February 9, it
reports 2010 (if you dump *all* 1..28 of February, Feb 9 is the only one
that jumps to the next year, strangely).
Cron spec (15 15 5 1 *) ok, startlater calc'd as (2009-01-05T15:15:00)
Cron spec (15 15 9 1 *) ok, startlater calc'd as (2009-01-09T15:15:00)
Cron spec (15 15 5 2 *) ok, startlater calc'd as (2009-02-05T15:15:00)
Cron spec (15 15 9 2 *) ok, startlater calc'd as (2010-02-09T15:15:00)
Chokes on March again.
Cron (15 15 5 3 *) calculation timed out, retrying with UTC
Cron spec (15 15 5 3 *) ok, startlater calc'd as (2008-03-05T15:15:00)
Cron (15 15 9 3 *) calculation timed out, retrying with UTC
Cron spec (15 15 9 3 *) ok, startlater calc'd as (2008-03-09T15:15:00)
The rest is good up till ...
Cron spec (15 15 5 4 *) ok, startlater calc'd as (2007-04-05T15:15:00)
Cron spec (15 15 9 4 *) ok, startlater calc'd as (2007-04-09T15:15:00)
Cron spec (15 15 5 5 *) ok, startlater calc'd as (2007-05-05T15:15:00)
Cron spec (15 15 9 5 *) ok, startlater calc'd as (2007-05-09T15:15:00)
Cron spec (15 15 5 6 *) ok, startlater calc'd as (2007-06-05T15:15:00)
Cron spec (15 15 9 6 *) ok, startlater calc'd as (2007-06-09T15:15:00)
Cron spec (15 15 5 7 *) ok, startlater calc'd as (2007-07-05T15:15:00)
Cron spec (15 15 9 7 *) ok, startlater calc'd as (2007-07-09T15:15:00)
Cron spec (15 15 5 8 *) ok, startlater calc'd as (2007-08-05T15:15:00)
Cron spec (15 15 9 8 *) ok, startlater calc'd as (2007-08-09T15:15:00)
Cron spec (15 15 5 9 *) ok, startlater calc'd as (2007-09-05T15:15:00)
Cron spec (15 15 9 9 *) ok, startlater calc'd as (2007-09-09T15:15:00)
Cron spec (15 15 5 10 *) ok, startlater calc'd as (2007-10-05T15:15:00)
Cron spec (15 15 9 10 *) ok, startlater calc'd as (2007-10-09T15:15:00)
November, when it jumps to 2008.
Cron spec (15 15 5 11 *) ok, startlater calc'd as (2008-11-05T15:15:00)
Cron spec (15 15 9 11 *) ok, startlater calc'd as (2008-11-09T15:15:00)
Cron spec (15 15 5 12 *) ok, startlater calc'd as (2008-12-05T15:15:00)
Cron spec (15 15 9 12 *) ok, startlater calc'd as (2008-12-09T15:15:00)
Ubuntu 6.10, DateTime::Event::Cron 0.07 and the dependencies it was able
to pull in from the standard distribution. If you can't reproduce this,
please let me know and I'll add more details.
Subject: | crontestcase.pl |
#!/usr/bin/perl
#
#
sub debug (@ ) { print @_, "\n" };
sub next_time_of_cron_spec ($ );
my $curtime = 1175307668; # March 30 2007
$scriptstartdt = DateTime->from_epoch(epoch => $curtime);
$scriptstartdt->set_time_zone('America/Los_Angeles');
debug "Script start is $scriptstartdt";
foreach $month (qw(jan feb mar apr may jun jul aug sep oct nov dec)) {
foreach $dom (5,10) {
next_time_of_cron_spec("15 15 $dom $month *");
}
}
$scriptstartdt += DateTime::Duration->new(seconds => 86400 * 3); # Apr 2
debug "Script start is $scriptstartdt";
foreach $month (qw(jan feb mar apr may jun jul aug sep oct nov dec)) {
foreach $dom (5, 9) {
next_time_of_cron_spec("15 15 $dom $month *");
}
}
sub next_time_of_cron_spec ($ ) {
my ($cronspec) = @_;
my $valid = undef;
use DateTime::Event::Cron;
# DateTime object representing next occurrence per cron spec
my $startlaterdt;
# DateTime::Set of cron occurrences
my $crondtset;
# need an eval because DateTime::Event::Cron croak()'s on an invalid cron
# spec.
# We standardize 'now' to be the start time of the scheduler script.
# This is to eliminate dependencies on how long various parts of the
# script took to run and to avoid timing issues when stepping through
# code in the debugger.
if ($cronspec =~ m/\S/) {
# handle named months and dow per crontab(5) -- currently
# DateTime::Event::Cron cant handle named months/dow, so we simulate it
# here
INIT {
%calnametonum = qw(jan 1 feb 2 mar 3 apr 4 may 5 jun 6
jul 7 aug 8 sep 9 oct 10 nov 11 dec 12
mon 1 tue 2 wed 3 thu 4 fri 5 sat 6 sun 7);
$calregexp = '(' . join('|', keys %calnametonum) . ')';
};
$cronspec = lc $cronspec;
$cronspec =~ s/$calregexp/$calnametonum{$1}/gi;
eval { $crondtset = DateTime::Event::Cron->from_cron
(cron=>$cronspec, after=>$scriptstartdt); };
}
if (ref $crondtset) {
# ugly ugly hack -- DateTime::Event::Cron has a bug where it hangs on
# calculating next year current month for certain cron specs
# e.g., today is 2007-3-30, calculating 23 59 3 29 * hangs
# so we alarm it and recalculate using utc if problems
eval {
# NB: newline required in die
local $SIG{ALRM} = sub { die "alarm\n" };
alarm 2;
$startlaterdt = $crondtset->next;
alarm 0;
};
if ($@) {
# propagate unexpected errors
die unless $@ eq "alarm\n";
debug "Cron ($cronspec) calculation timed out, retrying with UTC";
my $scriptstartdtutc = $scriptstartdt->clone;
$scriptstartdtutc->set_time_zone('UTC');
$crondtset = DateTime::Event::Cron->from_cron
(cron=>$cronspec, after=>$scriptstartdtutc);
$startlaterdt = $crondtset->next;
$valid=1;
} else {
$valid=1;
}
debug "Cron spec ($cronspec) ok, startlater calc'd as ($startlaterdt)";
}
ref $startlaterdt or do {
my $delay = 5;
use DateTime::Duration;
debug "Cron spec ($cronspec) not parseable";
$startlaterdt =
$scriptstartdt->clone + DateTime::Duration->new(seconds => $delay);
$valid = 0;
};
# times are passed around as strings, and those strings are to
# be interpreted in the timezone local to the scheduler machine
# $startlaterdt->set_time_zone('local');
# $startlaterdt stringifies to sql-friendly ISO8601 date format
return ("$startlaterdt", $valid);
}