2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 06:39:11 +00:00

Cron.pm: release

git-svn-id: https://svn.fhem.de/fhem/trunk@27915 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
herrmannj 2023-08-31 18:59:54 +00:00
parent d0924ad44c
commit 6d8599d2fd
4 changed files with 148 additions and 47 deletions

View File

@ -50,10 +50,25 @@ sub new {
$ok ||= any { sub{ my $a = shift; any { $a == $_ } (1, 3, 5, 7, 8, 10, 12) }->($_) } @{$self->{list_of_months}} if ($first == 31);
if (not $ok) {
$self->{error} = "day and month will never become true";
$self->log(2, '%s', $self->{error}) if $ENV{EXTENDED_DEBUG};
$self->log(2, '%s', $self->{error});
last VALIDATE;
}
}
if (exists($param->{holidays})) {
if (defined($param->{holidays}) and
ref($param->{holidays}) eq 'ARRAY' and
all {m/^[0-6]$/} @$param->{holidays}) {
@{$self->{config}->{holidays}} = sort { $a <=> $b } @$param->{holidays};
} else {
$self->{error} = "holidays must be a list with [0-6]";
$self->log(2, '%s', $self->{error});
last VALIDATE;
}
} else {
$self->{config}->{holidays} = [0,6]; #default weekend sun, sat
}
}
# $self->log(2, '%s', $self->{error}) if ($self->{error} and $ENV{EXTENDED_DEBUG});
return wantarray ? ($self, $self->{error}) : $self;
@ -69,13 +84,17 @@ sub next {
if ($self->{error}) { return wantarray ? (undef, $self->{error}) : undef };
# validate input
my $from_date = substr($from, 0, 8);
# internal method
return $self->_next($from);
my (@from_date, @from_time);
@from_date = ($from =~ m/^([0-9]{4})([0-9]{2})([0-9]{2})[0-9]{6}$/);
@from_time = ($from =~ m/^[0-9]{8}([0-9]{2})([0-9]{2})([0-9]{2})$/);
if (@from_date and $self->is_valid_date(@from_date) and
@from_time and $self->is_valid_time(@from_time)) {
# internal method
return $self->_next($from);
} else {
$self->log(2, 'wrong date or time spec in next call %s', $from);
return wantarray ? (undef, sprintf('wrong date or time spec in call to next() %s', $from)) : undef ;
}
}
sub _next {
@ -93,10 +112,17 @@ sub _next {
# If there is a discrepancy between from_date and working_date, we must first perform the next_date calculation.
# Otherwise, this has already been done in previous rounds and can be skipped for efficiency and speed reasons.
if ($from_date ne $self->{working_date}) {
# reset pointer
undef $self->{month_ptr};
undef $self->{mday_ptr};
undef $self->{positional_date_cache};
# reset pointer / cache # can happen because of sleep, hybernate, clock shift
$self->log(5, 'from_date:%s does not match working_date:%s -> clear cache ', $from_date, $self->{working_date}) if $ENV{EXTENDED_DEBUG};
# say "before: \n".join "\n", keys %{$self};
delete $self->{month_ptr};
delete $self->{weekday_month_ptr};
delete $self->{mday_ptr};
delete $self->{next_calender_date};
delete $self->{next_weekday_date};
delete $self->{next_positional_date};
delete $self->{positional_date_cache};
# say "after: \n".join "\n", keys %{$self};
$self->{working_date} = $next_date = $self->_next_date($from_date, 1); # inclusive, the from_date is possible
} else {
# load from cache
@ -107,6 +133,7 @@ sub _next {
# ($from_date ne $next_date)) {
if ($from_time ne $self->{working_time}) {
# reset pointer
$self->log(5, 'from_time:%s does not match working_time:%s -> clear cache ', $from_time, $self->{working_time}) if $ENV{EXTENDED_DEBUG};
undef $self->{hour_ptr};
undef $self->{minute_ptr};
}
@ -250,10 +277,18 @@ sub _parse_cron_text {
if (($mday ne '*' and $wday ne '*' ) or
($mday eq '*' and $wday ne '*' )) {
foreach my $item (@list) {
# to provide error glues if $item is malformed. $item may be modified below
my $item_txt = $item;
$self->log(5, 'about to parse wday item: %s', $item) if $ENV{EXTENDED_DEBUG};
# replace weekday abbreviation
my %w = (Sun => 0, Mon => 1, Tue => 2, Wed => 3, Thu => 4, Fr => 5, Sat => 6);
$item =~ s/(Sun|Mon|Tue|Wed|Thu|Sat)/$w{$1}/gie;
# my %w = (sun => 0, mon => 1, tue => 2, wed => 3, thu => 4, fri => 5, sat => 6);
my ($sep, $mod);
($item, $sep, $mod) = ($item =~ m/^([^#\/]*)([#\/]*)([^#\/]*)$/);
my %w = (sun => 0, mon => 1, tue => 2, wed => 3, thu => 4, fri => 5, sat => 6);
$item =~ s/(?<![a-z])(sun|mon|tue|wed|thu|fri|sat)(?![a-z])/$w{lc($1)}/gie;
$item .= $sep if defined($sep);
$item .= $mod if defined($mod);
# RULE 4
$item =~ s/^&//s if ($mday eq '*' and $item =~ m/^&(?!&).*/s);
@ -274,8 +309,8 @@ sub _parse_cron_text {
# $res &&= $self->_parse_wday_item($item, $href_and) if ($item =~ m/^[&].*/s);
if (not $res) {
$self->{error} = "syntax error in wday item: $item";
$self->log(5, 'syntax error in wday item: %s', $item) if $ENV{EXTENDED_DEBUG};
$self->{error} = "syntax error in wday item: $item_txt";
$self->log(5, 'syntax error in wday item: %s', $item_txt) if $ENV{EXTENDED_DEBUG};
return;
}
}

View File

@ -27,30 +27,44 @@ my $test = [
[q(handle '$cron_text'), '1-5 * * * *', qr(^$), sub {my @r = (20230101000000); for my $h (0..23) {for my $m (1..5) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(handle '$cron_text'), '1-5/1 * * * *', qr(^$), sub {my @r = (20230101000000); for my $h (0..23) {for my $m (1..5) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(handle '$cron_text'), '1-5/2 * * * *', qr(^$), sub {my @r = (20230101000000); for my $h (0..23) {for my $m (1,3,5) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
# [q(accept '$cron_text'), '*/1 * * * *', 0, 0, qr(^$)],
# # positive tests for minute / value range
# [q(accept '$cron_text'), '0 * * * *', 0, join (',', ((20200101120000 .. 20200101120059 ), (20200101120100 .. 20200101120159 ))), qr(^$)],
# [q(accept '$cron_text'), '00 * * * *', 0, 0, qr(^$)],
# [q(accept '$cron_text'), '1 * * * *', 0, 0, qr(^$)],
# [q(accept '$cron_text'), '01 * * * *', 0, 0, qr(^$)],
# [q(accept '$cron_text'), '1-59 * * * *', 0, 0, qr(^$)],
# [q(accept '$cron_text'), '01-059 * * * *', 0, 0, qr(^$)],
# [q(accept '$cron_text'), '1-59/1 * * * *', 0, 0, qr(^$)],
# [q(accept '$cron_text'), '01-059/01 * * * *', 0, 0, qr(^$)],
# [q(accept '$cron_text'), '1-59/59 * * * *', 0, 0, qr(^$)],
# [q(accept '$cron_text'), '01-059/059 * * * *', 0, 0, qr(^$)],
# [q(accept '$cron_text'), '10~20 * * * *', 0, 0, qr(^$)],
# # negative tests for minute / syntax
# [q(must throw an error '$cron_text'), 'a * * * *', 0, 0, qr(^syntax error in minute item:)],
# [q(must throw an error '$cron_text'), '*,a * * * *', 0, 0, qr(^syntax error in minute item:)],
# [q(must throw an error '$cron_text'), '1,a * * * *', 0, 0, qr(^syntax error in minute item:)],
# [q(must throw an error '$cron_text'), '*-5 * * * *', 0, 0, qr(^syntax error in minute item:)],
# [q(must throw an error '$cron_text'), '5-1 * * * *', 0, 0, qr(^syntax error in minute item:)],
# [q(must throw an error '$cron_text'), '60 * * * *', 0, 0, qr(^syntax error in minute item:)],
# [q(must throw an error '$cron_text'), '0-60 * * * *', 0, 0, qr(^syntax error in minute item:)],
# [q(must throw an error '$cron_text'), '0-59/60 * * * *', 0, 0, qr(^syntax error in minute item:)],
# [q(must throw an error '$cron_text'), '20~10 * * * *', 0, 0, qr(^syntax error in minute item:)],
# # negative tests for minute / value range
# positive tests for minute
[q(accept '$cron_text'), '0 * * * *', qr(^$), join (',', ((20230101120000 .. 20230101120059 ), (20230101120100 .. 20230101120159 )))],
[q(accept '$cron_text'), '00 * * * *', qr(^$), 0],
[q(accept '$cron_text'), '1 * * * *', qr(^$), 0],
[q(accept '$cron_text'), '01 * * * *', qr(^$), 0],
[q(accept '$cron_text'), '1-59 * * * *', qr(^$), sub {my @r = (20230101000000); for my $h (0) {for my $m (1..59) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(accept '$cron_text'), '01-059 * * * *', qr(^$), 0],
[q(accept '$cron_text'), '1-59/1 * * * *', qr(^$), sub {my @r = (20230101000000); for my $h (0) {for my $m (1..59) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(accept '$cron_text'), '1-59/30 * * * *', qr(^$), sub {my @r = (20230101000000); for my $h (0) {for my $m (1,31) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(accept '$cron_text'), '01-059/01 * * * *', qr(^$), 0],
[q(accept '$cron_text'), '1-59/59 * * * *', qr(^$), sub {my @r = (20230101000000); for my $h (0) {for my $m (1) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(accept '$cron_text'), '01-059/059 * * * *', qr(^$), 0],
[q(accept '$cron_text'), '10~20 * * * *', qr(^$), 0],
[q(accept '$cron_text'), '10~20,30~40 * * * *', qr(^$), 0],
# negative tests for minute
[q(reject '$cron_text'), 'a * * * *', qr(^syntax error in minute item:), 0],
[q(reject '$cron_text'), '*,a * * * *', qr(^syntax error in minute item:), 0],
[q(reject '$cron_text'), '1,a * * * *', qr(^syntax error in minute item:), 0],
[q(reject '$cron_text'), '*-5 * * * *', qr(^syntax error in minute item:), 0],
[q(reject '$cron_text'), '5-1 * * * *', qr(^syntax error in minute item:), 0],
[q(reject '$cron_text'), '60 * * * *', qr(^syntax error in minute item:), 0],
[q(reject '$cron_text'), '0-60 * * * *', qr(^syntax error in minute item:), 0],
[q(reject '$cron_text'), '0-59/60 * * * *', qr(^syntax error in minute item:), 0],
[q(reject '$cron_text'), '20~10 * * * *', qr(^syntax error in minute item:), 0],
# positive test for hour
[q(accept '$cron_text'), '0 0 * * *', qr(^$), 20230101000000, 20230102000000, 20230103000000],
[q(accept '$cron_text'), '0 00 * * *', qr(^$), 0],
[q(accept '$cron_text'), '0 1 * * *', qr(^$), 0],
[q(accept '$cron_text'), '0 01 * * *', qr(^$), 0],
[q(accept '$cron_text'), '0 1-23 * * *', qr(^$), sub {my @r = (20230101000000); for my $h (1..23) {for my $m (0) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(accept '$cron_text'), '0 01-023 * * *', qr(^$), sub {my @r = (20230101000000); for my $h (1..23) {for my $m (0) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(accept '$cron_text'), '0 1-23/1 * * *', qr(^$), sub {my @r = (20230101000000); for my $h (1..23) {for my $m (0) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(accept '$cron_text'), '0 01-023/01 * * *', qr(^$), sub {my @r = (20230101000000); for my $h (1..23) {for my $m (0) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(accept '$cron_text'), '0 1-23/12 * * *', qr(^$), sub {my @r = (20230101000000); for my $h (1,13) {for my $m (0) {push @r, sprintf('20230101%02d%02d00', $h, $m)}}; @r}->()],
[q(accept '$cron_text'), '0 1-23/23 * * *', qr(^$), 20230101010000, 20230102010000, 20230103010000],
[q(accept '$cron_text'), '0 1~10 * * *', qr(^$), 0],
[q(accept '$cron_text'), '0 1~10,11~20 * * *', qr(^$), 0],
# positive tests for weekday
[q(handle '$cron_text'), '0 0 * * *', qr(^$), sub {my @r = (20230101000000); for my $d (2..31) {for my $h (0) {for my $m (0) {push @r, sprintf('202301%02d%02d%02d00', $d, $h, $m)}}}; @r}->()],
@ -60,20 +74,37 @@ my $test = [
[q(handle '$cron_text'), '0 0 * * 1-5/2', qr(^$), 20230101000000, 20230102000000, 20230104000000, 20230106000000 ],
[q(handle '$cron_text'), '0 0 * * 1-5,6', qr(^$), 20230101000000, 20230102000000, 20230103000000, 20230104000000, 20230105000000, 20230106000000, 20230107000000 ],
[q(handle '$cron_text'), '0 0 8 * 1-5,6', qr(^$), 20230101000000, 20230102000000, 20230103000000, 20230104000000, 20230105000000, 20230106000000, 20230107000000, 20230108000000 ],
[q(handle '$cron_text'), '0 0 8 * Mon-Fri,Sat', qr(^$), 20230101000000, 20230102000000, 20230103000000, 20230104000000, 20230105000000, 20230106000000, 20230107000000, 20230108000000 ],
[q(handle '$cron_text'), '0 0 8 * mon-fri,sat', qr(^$), 20230101000000, 20230102000000, 20230103000000, 20230104000000, 20230105000000, 20230106000000, 20230107000000, 20230108000000 ],
[q(handle '$cron_text'), '0 0 8 * MON-FRI,SAT', qr(^$), 20230101000000, 20230102000000, 20230103000000, 20230104000000, 20230105000000, 20230106000000, 20230107000000, 20230108000000 ],
[q(handle '$cron_text'), '0 0 2-8 * &1-5,6', qr(^$), 20230101000000, 20230102000000, 20230103000000, 20230104000000, 20230105000000, 20230106000000, 20230107000000, 20230114000000 ],
[q(handle '$cron_text'), '0 0 2-8 * &Mon-Fri,Sat', qr(^$), 20230101000000, 20230102000000, 20230103000000, 20230104000000, 20230105000000, 20230106000000, 20230107000000, 20230114000000 ],
# positional
[q(handle '$cron_text'), '0 0 * * 4#l', qr(^$), 20230101000000, 20230126000000, 20230223000000, 20230330000000, 20230427000000, 20230525000000, 20230629000000 ],
[q(handle '$cron_text'), '0 0 * 2 0#F', qr(^$), 20230201000000, 20230205000000, 20240204000000, 20250202000000, 20260201000000, 20270207000000, 20280206000000 ],
[q(handle '$cron_text'), '0 0 * 2 Sun#F', qr(^$), 20230201000000, 20230205000000, 20240204000000, 20250202000000, 20260201000000, 20270207000000, 20280206000000 ],
[q(handle '$cron_text'), '0 0 * * 4#L', qr(^$), 20230101000000, 20230126000000, 20230223000000, 20230330000000, 20230427000000, 20230525000000, 20230629000000 ],
[q(handle '$cron_text'), '0 0 * * Thu#L', qr(^$), 20230101000000, 20230126000000, 20230223000000, 20230330000000, 20230427000000, 20230525000000, 20230629000000 ],
[q(handle '$cron_text'), '0 0 * 2 6#5', qr(^$), 20230201000000, 20480229000000, 20760229000000, 21160229000000, 21440229000000, 21720229000000, 22120229000000 ],
[q(handle '$cron_text'), '0 0 * 2 6#-5', qr(^$), 20230201000000, 20480201000000, 20760201000000, 21160201000000, 21440201000000, 21720201000000, 22120201000000 ],
# reject
[q(reject '$cron_text'), '0 0 * 2 8', qr(^syntax error in wday item: 8$), 20230201000000 ],
[q(reject '$cron_text'), '0 0 * 2 0/Mon', qr(^syntax error in wday item: 0/Mon$), 20230201000000 ],
[q(reject '$cron_text'), '0 0 * 2 0#0', qr(^syntax error in wday item: 0#0$), 20230201000000 ],
[q(reject '$cron_text'), '0 0 * 2 0#6', qr(^syntax error in wday item: 0#6$), 20230201000000 ],
[q(reject '$cron_text'), '0 0 * 2 0#-6', qr(^syntax error in wday item: 0#-6$), 20230201000000 ],
[q(reject '$cron_text'), '0 0 * 2 0#A', qr(^syntax error in wday item: 0#A$), 20230201000000 ],
[q(reject '$cron_text'), '0 0 * 2 0#Mon', qr(^syntax error in wday item: 0#Mon$), 20230201000000 ],
[q(reject '$cron_text'), '0 0 * 2 FOO', qr(^syntax error in wday item: FOO$), 20230201000000 ],
[q(reject '$cron_text'), '0 0 * 2 Sunday', qr(^syntax error in wday item: Sunday$), 20230201000000 ],
# time series
[q(Timeseries '$cron_text'), '0 12 3,4,5 2 0,2,3,4', qr(^$), 20230102150000, 20230201120000, 20230202120000, 20230203120000, 20230204120000],
[q(Feb-29 & Sunday '$cron_text'), '0 12 29 2 &7', qr(^$), 20230102150000, 20320229120000],
[q(Timeseries '$cron_text'), '0 12 3,4,5 2 0,2,3,4', qr(^$), 20230102150000, 20230201120000, 20230202120000, 20230203120000, 20230204120000],
[q(Feb-29 (leap year)'$cron_text'), '0 12 29 2 *', qr(^$), 20230101150000, 20240229120000, 20280229120000, 20320229120000],
[q(Feb-29 & Sunday '$cron_text'), '0 12 29 2 &Sun', qr(^$), 20230102150000, 20320229120000],
[q(Fri 13 '$cron_text'), '0 12 13 * &Fri', qr(^$), 20230101150000, 20230113120000, 20231013120000, 20240913120000],
[q(daylight eu '$cron_text'), '0 2 * 3,10 Sun#L', qr(^$), 20230101150000, 20230326020000, 20231029020000, 20240331020000],
];
# print join ",", (20200101120000 .. 20200101120010 ), (20200101120100 .. 20200101120110 );
# print "\n";
my $cron_lib_loadable = eval{use FHEM::Scheduler::Cron;1;};
ok($cron_lib_loadable, "FHEM::Scheduler::Cron loaded");
@ -94,7 +125,7 @@ foreach my $t (@$test) {
}
}
$ok &&= (($err // '') =~ /$err_expected/);
ok($ok, sprintf('%s %s', eval qq{"$desc"} , $err?"(got '$err')":"(# of passes: $count)"));
ok($ok, sprintf('%s %s', eval qq{"$desc"} , $err?"(got '$err')":"(# of successful passes: $count)"));
};
done_testing;

View File

View File

@ -0,0 +1,35 @@
# perl fhem.pl -t t/FHEM/90_Cron/99_Cronlib_DEBUG.t
use v5.14;
use strict;
use warnings;
use Test::More;
use FHEM::Scheduler::Cron;
$ENV{EXTENDED_DEBUG} = 1;
my ($cron_obj, $err, $got);
($cron_obj, $err) = FHEM::Scheduler::Cron->new("1 12,13 1-15 * 2#1");
($got, $err) = $cron_obj->next(20230101120000);
ok((not $err and ($got) and ($got eq 20230101120100)), "after new: $got");
($got, $err) = $cron_obj->next(20230101120100);
ok((not $err and ($got) and ($got eq 20230101130100)), "time cache: $got");
($got, $err) = $cron_obj->next(20230101130100);
ok((not $err and ($got) and ($got eq 20230102120100)), "time cache: $got");
($got, $err) = $cron_obj->next(20230102120100);
ok((not $err and ($got) and ($got eq 20230102130100)), "time cache: $got");
($got, $err) = $cron_obj->next(20230102130100);
ok((not $err and ($got) and ($got eq 20230103120100)), "time cache: $got");
($got, $err) = $cron_obj->next(20230103120100);
ok((not $err and ($got) and ($got eq 20230103130100)), "time cache: $got");
($got, $err) = $cron_obj->next(20230103130100);
ok((not $err and ($got) and ($got eq 20230104120100)), "time cache: $got");
# jumps
($got, $err) = $cron_obj->next(20230105130500);
ok((not $err and ($got) and ($got eq 20230106120100)), "jump forward: $got");
($got, $err) = $cron_obj->next(20230101120000);
ok((not $err and ($got) and ($got eq 20230101120100)), "jump forward: $got");
done_testing;
exit(0);