2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-17 11:26:03 +00:00

Cron.pm: wip

git-svn-id: https://svn.fhem.de/fhem/trunk@27888 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
herrmannj 2023-08-23 23:15:51 +00:00
parent ea6547420a
commit 90c5b237b1

View File

@ -96,6 +96,7 @@ sub _next {
# reset pointer
undef $self->{month_ptr};
undef $self->{mday_ptr};
undef $self->{positional_date_cache};
$self->{working_date} = $next_date = $self->_next_date($from_date, 1); # inclusive, the from_date is possible
} else {
# load from cache
@ -234,9 +235,9 @@ sub _parse_cron_text {
$self->log(5, 'list_of_months: %s', join(',', @{$self->{list_of_months}})) if $ENV{EXTENDED_DEBUG};
}
{
my $href_or = {};
{ my $href_positional = {};
my $href_and = {};
my $href_or = {};
my @list = split(',', $wday);
# weired mday/wday logic:
# (1) if mday = * and wday = * then expand mdays (DO NOT expand wdays) (it would work but it doesnt make sense to use both)
@ -261,8 +262,8 @@ sub _parse_cron_text {
# most specific to most generic
if ($item =~ m/^[w,h]$/i) { # - weekdays, holidays
$res &&= 0;
} elsif ($item =~ m/^[0-7]#[1-5,f,l]$/i) { # - positional weekday
$res &&= 0;
} elsif ($item =~ m/^0*[0-7]#(?|-*0*[1-5]|0*[1-5]|[f,l])$/i) { # - positional weekday
$res &&= $self->_parse_positional_wday_item($item, $href_positional);
} elsif ($item =~ m/^&.*/s) { # - and logic
$res &&= $self->_parse_wday_item($item, $href_and);
} else { # - anything else
@ -282,10 +283,12 @@ sub _parse_cron_text {
$self->log(5, 'ignore wdays because of mday/wday logic') if $ENV{EXTENDED_DEBUG};
}
# numerical sort keys and create the list
@{$self->{list_of_or_wdays}} = sort { $a <=> $b } keys %$href_or;
$self->log(5, 'list_of_or_wdays: %s', join(',', @{$self->{list_of_or_wdays}})) if $ENV{EXTENDED_DEBUG};
@{$self->{list_of_positional_wdays}} = keys %$href_positional;
$self->log(5, 'list_of_positional_wdays: %s', join(',', @{$self->{list_of_positional_wdays}})) if $ENV{EXTENDED_DEBUG};
@{$self->{list_of_and_wdays}} = sort { $a <=> $b } keys %$href_and;
$self->log(5, 'list_of_and_wdays: %s', join(',', @{$self->{list_of_and_wdays}})) if $ENV{EXTENDED_DEBUG};
@{$self->{list_of_or_wdays}} = sort { $a <=> $b } keys %$href_or;
$self->log(5, 'list_of_or_wdays: %s', join(',', @{$self->{list_of_or_wdays}})) if $ENV{EXTENDED_DEBUG};
}
return 1;
}
@ -387,6 +390,20 @@ sub _parse_month_item {
return 1;
}
# wdays expands to 0..6 but accept 0..7
sub _parse_positional_wday_item {
my ($self, $in, $href) = @_;
my ($wday, $position);
($wday, $position) = ($in =~ m/^0*([0-7])#(?|(-*0*[1-5])|(0*[1-5])|([f,l]))$/i);
return if (not defined($wday) and not defined($position)); # syntax error
$position =~ s/f/1/i;
$position =~ s/l/-1/i;
$href->{"$wday#$position"} = 1;
return 1;
}
# wdays expands to 0..6 but accept 0..7
sub _parse_wday_item {
my ($self, $in, $href) = @_;
@ -430,7 +447,7 @@ sub _next_date {
# check if cached value can be used
if (($self->{working_date} and $self->{next_calender_date}) and
(($inclusive and $self->{next_calender_date} >= $self->{working_date}) or
($self->{next_calender_date} > $self->{working_date}))) {
(not $inclusive and $self->{next_calender_date} > $self->{working_date}))) {
$self->log(5, 'use cached calender date %04d-%02d-%02d', ($self->{next_calender_date} =~ m/(\d{4})(\d{2})(\d{2})/)) if $ENV{EXTENDED_DEBUG};
push @set, $self->{next_calender_date};
} else {
@ -442,13 +459,25 @@ sub _next_date {
# check if cached value can be used
if (($self->{working_date} and $self->{next_weekday_date}) and
(($inclusive and $self->{next_weekday_date} >= $self->{working_date}) or
($self->{next_weekday_date} > $self->{working_date}))) {
(not $inclusive and $self->{next_weekday_date} > $self->{working_date}))) {
$self->log(5, 'use cached weekday date %04d-%02d-%02d', ($self->{next_weekday_date} =~ m/(\d{4})(\d{2})(\d{2})/)) if $ENV{EXTENDED_DEBUG};
push @set, $self->{next_weekday_date};
} else {
push @set, $self->{next_weekday_date} = $self->_next_weekday_date($from_date, $inclusive);
}
}
# positional
if (scalar @{$self->{list_of_positional_wdays}}) {
# check if cached value can be used
if (($self->{working_date} and $self->{next_positional_date}) and
(($inclusive and $self->{next_positional_date} >= $self->{working_date}) or
(not $inclusive and $self->{next_positional_date} > $self->{working_date}))) {
$self->log(5, 'use cached positional date %04d-%02d-%02d', ($self->{next_positional_date} =~ m/(\d{4})(\d{2})(\d{2})/)) if $ENV{EXTENDED_DEBUG};
push @set, $self->{next_positional_date};
} else {
push @set, $self->{next_positional_date} = $self->_next_positional_date($from_date, $inclusive);
}
}
# whatever comes first
my $result = min @set;
@ -502,7 +531,7 @@ sub _next_calendar_date {
# date is greater to the 'from' date (NOT inclusive)
if ($self->is_valid_date($y, $m, $d) and (
($inclusive and $candidate >= sprintf('%04d%02d%02d', $year, $month, $mday)) or
($candidate > sprintf('%04d%02d%02d', $year, $month, $mday)))) {
(not $inclusive and $candidate > sprintf('%04d%02d%02d', $year, $month, $mday)))) {
# weekdays with 'and' logic
if (scalar @{$self->{list_of_and_wdays}}) {
$self->log(5, 'test candidate %04d-%02d-%02d on wday %d against \'and\' list %s', $y, $m, $d, $self->get_weekday($y, $m, $d), join ',',@{$self->{list_of_and_wdays}}) if $ENV{EXTENDED_DEBUG};
@ -562,9 +591,9 @@ sub _next_weekday_date {
# }
my $found = undef;
my $year_next = $year;
# my $year_next = $year;
while (not defined($found)) {
my ($y, $m) = ($year_next, $self->{list_of_months}->[$self->{weekday_month_ptr}]);
my ($y, $m) = ($year, $self->{list_of_months}->[$self->{weekday_month_ptr}]);
my $d = ($m != $month) ? 1 : $mday;
my $candidate = sprintf('%04d%02d%02d', $y, $m, $d);
$self->log(5, 'test candidate %04d-%02d-%02d', $y, $m, $d) if $ENV{EXTENDED_DEBUG};
@ -592,7 +621,7 @@ sub _next_weekday_date {
$self->{weekday_month_ptr} = 0;
$y++;
}
$m = $self->{list_of_months}->[$self->{weekday_month_ptr}];
# $m = $self->{list_of_months}->[$self->{weekday_month_ptr}];
$d = 1;
}
}
@ -600,6 +629,127 @@ sub _next_weekday_date {
return $found;
}
# positional weekdays:
# - 0#2 - second monday in month
# - 5#6 - 6th friday in month
# - 1#f - first monday
# - 0#l - last sunday
# $from_time: time from which the next time is to be determined as hhmmss
# $inclusive: TRUE: include from time (>=), FALSE: exclude $from_time (>)
sub _next_positional_date {
my $self = shift;
my $from_date = shift;
my $inclusive = shift;
my ($year, $month, $mday) = ($from_date =~ m/(\d{4})(\d{2})(\d{2})/);
$self->log(5, 'search next positional date %s %04d-%02d-%02d',
$inclusive?'at or after (inclusive)':'after (exclusive)',
$year, $month, $mday) if $ENV{EXTENDED_DEBUG};
my $month_ptr;
my @res;
# # initialize ptr or use cached values
# # the ptr points to the correspondig list entry
# $self->{positional_month_ptr} //= sub {
# $self->log(5, 'initialize positional month pointer') if $ENV{EXTENDED_DEBUG};
# my $t = shift;
# my $i = 0;
# return 0 if ($self->{list_of_months}->[$i] >= $t);
# while ($self->{list_of_months}->[$i] < $t and $i < $#{$self->{list_of_months}}) {$i++}
# return $i}->($month);
# $self->log(5, 'pointer set: month %d(%d)', $self->{positional_month_ptr}, $self->{list_of_months}->[$self->{positional_month_ptr}]) if $ENV{EXTENDED_DEBUG};
for my $item (@{$self->{list_of_positional_wdays}}) {
if (defined($self->{positional_date_cache}->{$item})) {
if (($inclusive and $from_date > $self->{positional_date_cache}->{$item}) or
(not $inclusive and $from_date >= $self->{positional_date_cache}->{$item})) {
$self->log(5, 'delete expired cache entry for %s %d', $item, $self->{positional_date_cache}->{$item}) if $ENV{EXTENDED_DEBUG};
delete $self->{positional_date_cache}->{$item};
} else {
$self->log(5, 'use cache entry for %s %d', $item, $self->{positional_date_cache}->{$item}) if $ENV{EXTENDED_DEBUG};
push @res, $self->{positional_date_cache}->{$item};
}
}
if (not defined($self->{positional_date_cache}->{$item})) {
$self->log(5, 'create cache entry for %s', $item) if $ENV{EXTENDED_DEBUG};
$month_ptr //= sub {
$self->log(5, 'initialize positional month pointer') if $ENV{EXTENDED_DEBUG};
my $t = shift;
my $i = 0;
return 0 if ($self->{list_of_months}->[$i] >= $t);
while ($self->{list_of_months}->[$i] < $t and $i < $#{$self->{list_of_months}}) {$i++}
return $i}->($month);
$self->log(5, 'pointer set: month %d(%d)', $month_ptr, $self->{list_of_months}->[$month_ptr]) if $ENV{EXTENDED_DEBUG};
my ($wday, $position) = split '#', $item;
if ($position > 0) {
my $found;
my $year_next = $year;
my $temp_month_ptr = $month_ptr;
while (not defined($found)) {
my ($y, $m) = ($year_next, $self->{list_of_months}->[$temp_month_ptr]);
my $first_wday = $self->get_weekday($y, $m, 1);
my $day_diff = ($wday + 7 - $first_wday) %7;
$day_diff += 1 + (($position - 1) * 7);
my $candidate = sprintf('%04d%02d%02d', $y, $m, $day_diff);
$self->log(5, 'candidate %04d-%02d-%02d', $y, $m, $day_diff) if $ENV{EXTENDED_DEBUG};
if ($self->is_valid_date($y, $m, $day_diff) and
(($inclusive and $candidate >= $from_date) or
(not $inclusive and $candidate > $from_date))) {
push @res, $found = $candidate;
$self->log(5, 'found candidate %04d-%02d-%02d for %s', $y, $m, $day_diff, $item) if $ENV{EXTENDED_DEBUG};
# last and exit loop
} else {
$temp_month_ptr++;
if ($temp_month_ptr > $#{$self->{list_of_months}}) {
$temp_month_ptr = 0;
$year_next++;
}
}
}
} elsif ($position < 0) {
my @days_in_month = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
my $found;
my $year_next = $year;
my $temp_month_ptr = $month_ptr;
while (not defined($found)) {
my ($y, $m) = ($year_next, $self->{list_of_months}->[$temp_month_ptr]);
# Leap year check
if ($m == 2 && (($y % 4 == 0 && $y % 100 != 0) || $y % 400 == 0)) {
$days_in_month[2] = 29;
} else {
$days_in_month[2] = 28;
}
my $last_day = $days_in_month[$m];
my $last_wday = $self->get_weekday($y, $m, $last_day);
my $day_diff = ($last_wday + 7 - $wday) %7;
$day_diff += (($position * -1) - 1) * 7;
my $candidate = sprintf('%04d%02d%02d', $y, $m, $days_in_month[$m] - $day_diff);
$self->log(5, 'candidate %04d-%02d-%02d', $y, $m, $days_in_month[$m] - $day_diff) if $ENV{EXTENDED_DEBUG};
if ($self->is_valid_date($y, $m, $days_in_month[$m] - $day_diff) and
(($inclusive and $candidate >= $from_date) or
(not $inclusive and $candidate > $from_date))) {
push @res, $found = $candidate;
$self->log(5, 'found candidate %04d-%02d-%02d for %s', $y, $m, $days_in_month[$m] - $day_diff, $item) if $ENV{EXTENDED_DEBUG};
# last and exit loop
} else {
$temp_month_ptr++;
if ($temp_month_ptr > $#{$self->{list_of_months}}) {
$temp_month_ptr = 0;
$year_next++;
}
}
}
}
}
}
return min @res;
}
sub get_weekday {
my $self = shift;
my ($year, $month, $day) = @_;
@ -724,74 +874,4 @@ sub _next_time {
return ($found, $day_changed);
}
# positional weekdays:
# - 0#2 - second monday in month
# - 5#6 - 6th friday in month
# - 1#f - first monday
# - 0#l - last sunday
# $from_time: time from which the next time is to be determined as hhmmss
# $inclusive: TRUE: include from time (>=), FALSE: exclude $from_time (>)
sub _next_positional_date {
my $self = shift;
my $from_time = shift;
my $inclusive = shift;
# printf('next weekday from: %4d%02d%02d %s', $year, $month, $mday, "\n");
# # return if cache has a value
# # test-candidate year
# my $y = $year;
# # test-candidate month
# # initialize cache if not done already
# if (not defined($self->{month_ptr_special})) {
# $self->{month_ptr_special} = first { print "test $#{$self->{list_of_months}} $_ \n"; $self->{list_of_months}->[$_] >= $month } 0 .. $#{$self->{list_of_months}};
# $self->{month_ptr_special} //= 0; # case month < first entry
# }
# my $m = $self->{list_of_months}->[$self->{month_ptr_special}];
# $y++ if $m < $month;
# my $d = ($m != $month)?1:$mday;
# printf('first special date candidate: %4d%02d%02d ptr: %d %s', $y, $m, $d, $self->{month_ptr_special}, "\n");
# # create a list with converted 'special' dates like 0#1 (first sunday in month)
# @{$self->{list_of_special_wdays}} = ('0#5');
# my @list;
# my $href;
# do {
# my $first_wday = $self->get_weekday($y, $m, 1);
# print "special date: first wday of $m is $first_wday \n";
# foreach my $item (@{$self->{list_of_special_wdays}}) {
# print "special item $item \n";
# my ($wday, $number) = ($item =~ m/^([0-6])#([1-6])$/g);
# print "special item: $item = $wday nr $number \n";
# my $day_diff = ($wday + 7 - $first_wday) %7;
# print "special item: day_diff $day_diff \n";
# $day_diff += ($number - 1) * 7;
# $day_diff += 1;
# print "special item: day_diff $day_diff \n";
# if ($self->is_valid_date($y, $m, $day_diff)) {
# printf ("add to list %d %d %d \n", $y, $m, $day_diff);
# push @list, sprintf('%4d%02d%02d', $y, $m, $day_diff);
# }
# }
# # advance ptr
# # first day of next available month
# $self->{month_ptr_special}++;
# if ($self->{month_ptr_special} > $#{$self->{list_of_months}}) {
# $self->{month_ptr_special} = 0;
# $y++;
# }
# $m = $self->{list_of_months}->[$self->{month_ptr_special}];
# $d = 1;
# } until (scalar @list)
}
1;