5
0
mirror of git://git.proxmox.com/git/pve-common.git synced 2025-01-03 09:17:36 +03:00

CalendarEvent: use rust implementation

by replacing the parsing code and 'compute_next_event' by their
PVE::RS::CalendarEvent equivalent

adapt the tests, since we do not have access to the internal structure
(and even if we had, it would be different) and the error messages
are different

the 'compute_next_event' and parsing tests still pass though

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
Dominik Csapak 2021-12-01 09:55:14 +01:00 committed by Thomas Lamprecht
parent a45a1df1ed
commit 9572e1046b
2 changed files with 23 additions and 270 deletions

View File

@ -6,6 +6,7 @@ use Data::Dumper;
use Time::Local; use Time::Local;
use PVE::JSONSchema; use PVE::JSONSchema;
use PVE::Tools qw(trim); use PVE::Tools qw(trim);
use PVE::RS::CalendarEvent;
# Note: This class implements a parser/utils for systemd like calendar exents # Note: This class implements a parser/utils for systemd like calendar exents
# Date specification is currently not implemented # Date specification is currently not implemented
@ -43,259 +44,13 @@ sub parse_calendar_event {
die "unable to parse calendar event - event is empty\n"; die "unable to parse calendar event - event is empty\n";
} }
my $parse_single_timespec = sub { return PVE::RS::CalendarEvent->new($event);
my ($p, $max, $matchall_ref, $res_hash) = @_;
if ($p =~ m/^((?:\*|[0-9]+))(?:\/([1-9][0-9]*))?$/) {
my ($start, $repetition) = ($1, $2);
if (defined($repetition)) {
$repetition = int($repetition);
$start = $start eq '*' ? 0 : int($start);
die "value '$start' out of range\n" if $start >= $max;
die "repetition '$repetition' out of range\n" if $repetition >= $max;
while ($start < $max) {
$res_hash->{$start} = 1;
$start += $repetition;
}
} else {
if ($start eq '*') {
$$matchall_ref = 1;
} else {
$start = int($start);
die "value '$start' out of range\n" if $start >= $max;
$res_hash->{$start} = 1;
}
}
} elsif ($p =~ m/^([0-9]+)\.\.([1-9][0-9]*)$/) {
my ($start, $end) = (int($1), int($2));
die "range start '$start' out of range\n" if $start >= $max;
die "range end '$end' out of range\n" if $end >= $max || $end < $start;
for (my $i = $start; $i <= $end; $i++) {
$res_hash->{$i} = 1;
}
} else {
die "unable to parse calendar event '$p'\n";
}
};
my $h = undef;
my $m = undef;
my $matchall_minutes = 0;
my $matchall_hours = 0;
my $minutes_hash = {};
my $hours_hash = {};
my $dowsel = join('|', keys %$dow_names);
my $dow_hash;
my $parse_dowspec = sub {
my ($p) = @_;
if ($p =~ m/^($dowsel)$/i) {
$dow_hash->{$dow_names->{lc($1)}} = 1;
} elsif ($p =~ m/^($dowsel)\.\.($dowsel)$/i) {
my $start = $dow_names->{lc($1)};
my $end = $dow_names->{lc($2)} || 7;
die "wrong order in range '$p'\n" if $end < $start;
for (my $i = $start; $i <= $end; $i++) {
$dow_hash->{($i % 7)} = 1;
}
} else {
die "unable to parse weekday specification '$p'\n";
}
};
my @parts = split(/\s+/, $event);
my $utc = (@parts && uc($parts[-1]) eq 'UTC');
pop @parts if $utc;
if ($parts[0] =~ m/$dowsel/i) {
my $dow_spec = shift @parts;
foreach my $p (split(',', $dow_spec)) {
$parse_dowspec->($p);
}
} else {
$dow_hash = { 0 => 1, 1 => 1, 2 => 1, 3 => 1, 4 => 1, 5=> 1, 6 => 1 };
}
if (scalar(@parts) && $parts[0] =~ m/\-/) {
my $date_spec = shift @parts;
die "date specification not implemented";
}
my $time_spec = shift(@parts) // "00:00";
my $chars = '[0-9*/.,]';
if ($time_spec =~ m/^($chars+):($chars+)$/) {
my ($p1, $p2) = ($1, $2);
foreach my $p (split(',', $p1)) {
$parse_single_timespec->($p, 24, \$matchall_hours, $hours_hash);
}
foreach my $p (split(',', $p2)) {
$parse_single_timespec->($p, 60, \$matchall_minutes, $minutes_hash);
}
} elsif ($time_spec =~ m/^($chars)+$/) { # minutes only
$matchall_hours = 1;
foreach my $p (split(',', $time_spec)) {
$parse_single_timespec->($p, 60, \$matchall_minutes, $minutes_hash);
}
} else {
die "unable to parse calendar event\n";
}
die "unable to parse calendar event - unused parts\n" if scalar(@parts);
if ($matchall_hours) {
$h = '*';
} else {
$h = [ sort { $a <=> $b } keys %$hours_hash ];
}
if ($matchall_minutes) {
$m = '*';
} else {
$m = [ sort { $a <=> $b } keys %$minutes_hash ];
}
return { h => $h, m => $m, dow => [ sort keys %$dow_hash ], utc => $utc };
}
sub is_leap_year($) {
return 0 if $_[0] % 4;
return 1 if $_[0] % 100;
return 0 if $_[0] % 400;
return 1;
}
# mon = 0.. (Jan = 0)
sub days_in_month($$) {
my ($mon, $year) = @_;
return 28 + is_leap_year($year) if $mon == 1;
return (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[$mon];
}
# day = 1..
# mon = 0.. (Jan = 0)
sub wrap_time($) {
my ($time) = @_;
my ($sec, $min, $hour, $day, $mon, $year, $wday) = @$time;
use integer;
if ($sec >= 60) {
$min += $sec / 60;
$sec %= 60;
}
if ($min >= 60) {
$hour += $min / 60;
$min %= 60;
}
if ($hour >= 24) {
$day += $hour / 24;
$wday += $hour / 24;
$hour %= 24;
}
# Translate to 0..($days_in_mon-1)
--$day;
while (1) {
my $days_in_mon = days_in_month($mon % 12, $year);
last if $day < $days_in_mon;
# Wrap one month
$day -= $days_in_mon;
++$mon;
}
# Translate back to 1..$days_in_mon
++$day;
if ($mon >= 12) {
$year += $mon / 12;
$mon %= 12;
}
$wday %= 7;
return [$sec, $min, $hour, $day, $mon, $year, $wday];
}
# helper as we need to keep weekdays in sync
sub time_add_days($$) {
my ($time, $inc) = @_;
my ($sec, $min, $hour, $day, $mon, $year, $wday) = @$time;
return wrap_time([$sec, $min, $hour, $day + $inc, $mon, $year, $wday + $inc]);
} }
sub compute_next_event { sub compute_next_event {
my ($calspec, $last) = @_; my ($calspec, $last) = @_;
my $hspec = $calspec->{h}; return $calspec->compute_next_event($last);
my $mspec = $calspec->{m};
my $dowspec = $calspec->{dow};
my $utc = $calspec->{utc};
$last += 60; # at least one minute later
my $t = [$utc ? gmtime($last) : localtime($last)];
$t->[0] = 0; # we're not interested in seconds, actually
$t->[5] += 1900; # real years for clarity
outer: for (my $i = 0; $i < 1000; ++$i) {
my $wday = $t->[6];
foreach my $d (@$dowspec) {
goto this_wday if $d == $wday;
if ($d > $wday) {
$t->[0] = $t->[1] = $t->[2] = 0; # sec = min = hour = 0
$t = time_add_days($t, $d - $wday);
next outer;
}
}
# Test next week:
$t->[0] = $t->[1] = $t->[2] = 0; # sec = min = hour = 0
$t = time_add_days($t, 7 - $wday);
next outer;
this_wday:
goto this_hour if $hspec eq '*';
my $hour = $t->[2];
foreach my $h (@$hspec) {
goto this_hour if $h == $hour;
if ($h > $hour) {
$t->[0] = $t->[1] = 0; # sec = min = 0
$t->[2] = $h; # hour = $h
next outer;
}
}
# Test next day:
$t->[0] = $t->[1] = $t->[2] = 0; # sec = min = hour = 0
$t = time_add_days($t, 1);
next outer;
this_hour:
goto this_min if $mspec eq '*';
my $min = $t->[1];
foreach my $m (@$mspec) {
goto this_min if $m == $min;
if ($m > $min) {
$t->[0] = 0; # sec = 0
$t->[1] = $m; # min = $m
next outer;
}
}
# Test next hour:
$t->[0] = $t->[1] = 0; # sec = min = hour = 0
$t->[2]++;
$t = wrap_time($t);
next outer;
this_min:
return $utc ? timegm(@$t) : timelocal(@$t);
}
die "unable to compute next calendar event\n";
} }
1; 1;

View File

@ -18,7 +18,7 @@ my $alldays = [0,1,2,3,4,5,6];
my $tests = [ my $tests = [
[ [
'*', '*',
{ h => '*', m => '*', dow => $alldays }, undef,
[ [
[0, 60], [0, 60],
[30, 60], [30, 60],
@ -28,7 +28,7 @@ my $tests = [
], ],
[ [
'*/10', '*/10',
{ h => '*', m => [0, 10, 20, 30, 40, 50], dow => $alldays }, undef,
[ [
[0, 600], [0, 600],
[599, 600], [599, 600],
@ -38,7 +38,7 @@ my $tests = [
], ],
[ [
'*/12:0' , '*/12:0' ,
{ h => [0, 12], m => [0], dow => $alldays }, undef,
[ [
[ 10, 43200], [ 10, 43200],
[ 13*3600, 24*3600], [ 13*3600, 24*3600],
@ -46,7 +46,7 @@ my $tests = [
], ],
[ [
'1/12:0/15' , '1/12:0/15' ,
{ h => [1, 13], m => [0, 15, 30, 45], dow => $alldays }, undef,
[ [
[0, 3600], [0, 3600],
[3600, 3600+15*60], [3600, 3600+15*60],
@ -61,7 +61,7 @@ my $tests = [
], ],
[ [
'1,4,6', '1,4,6',
{ h => '*', m => [1, 4, 6], dow => $alldays}, undef,
[ [
[0, 60], [0, 60],
[60, 4*60], [60, 4*60],
@ -71,15 +71,15 @@ my $tests = [
], ],
[ [
'0..3', '0..3',
{ h => '*', m => [ 0, 1, 2, 3 ], dow => $alldays }, undef,
], ],
[ [
'23..23:0..3', '23..23:0..3',
{ h => [ 23 ], m => [ 0, 1, 2, 3 ], dow => $alldays }, undef,
], ],
[ [
'Mon', 'Mon',
{ h => [0], m => [0], dow => [1] }, undef,
[ [
[0, 4*86400], # Note: Epoch 0 is Thursday, 1. January 1970 [0, 4*86400], # Note: Epoch 0 is Thursday, 1. January 1970
[4*86400, 11*86400], [4*86400, 11*86400],
@ -88,7 +88,7 @@ my $tests = [
], ],
[ [
'sat..sun', 'sat..sun',
{ h => [0], m => [0], dow => [0, 6] }, undef,
[ [
[0, 2*86400], [0, 2*86400],
[2*86400, 3*86400], [2*86400, 3*86400],
@ -97,7 +97,7 @@ my $tests = [
], ],
[ [
'sun..sat', 'sun..sat',
{ h => [0], m => [0], dow => $alldays }, undef,
], ],
[ [
'Fri..Mon', 'Fri..Mon',
@ -105,15 +105,15 @@ my $tests = [
], ],
[ [
'wed,mon..tue,fri', 'wed,mon..tue,fri',
{ h => [0], m => [0], dow => [ 1, 2, 3, 5] }, undef,
], ],
[ [
'mon */15', 'mon */15',
{ h => '*', m => [0, 15, 30, 45], dow => [1]}, undef,
], ],
[ [
'22/1:0', '22/1:0',
{ h => [22, 23], m => [0], dow => $alldays }, undef,
[ [
[0, 22*60*60], [0, 22*60*60],
[22*60*60, 23*60*60], [22*60*60, 23*60*60],
@ -122,7 +122,7 @@ my $tests = [
], ],
[ [
'*/2:*', '*/2:*',
{ h => [0,2,4,6,8,10,12,14,16,18,20,22], m => '*', dow => $alldays }, undef,
[ [
[0, 60], [0, 60],
[60*60, 2*60*60], [60*60, 2*60*60],
@ -131,7 +131,7 @@ my $tests = [
], ],
[ [
'20..22:*/30', '20..22:*/30',
{ h => [20,21,22], m => [0,30], dow => $alldays }, undef,
[ [
[0, 20*60*60], [0, 20*60*60],
[20*60*60, 20*60*60 + 30*60], [20*60*60, 20*60*60 + 30*60],
@ -164,7 +164,7 @@ my $tests = [
], ],
[ [
'0,1,3..5', '0,1,3..5',
{ h => '*', m => [0,1,3,4,5], dow => $alldays }, undef,
[ [
[0, 60], [0, 60],
[60, 3*60], [60, 3*60],
@ -173,7 +173,7 @@ my $tests = [
], ],
[ [
'2,4:0,1,3..5', '2,4:0,1,3..5',
{ h => [2,4], m => [0,1,3,4,5], dow => $alldays }, undef,
[ [
[0, 2*60*60], [0, 2*60*60],
[2*60*60 + 60, 2*60*60 + 3*60], [2*60*60 + 60, 2*60*60 + 3*60],
@ -185,18 +185,16 @@ my $tests = [
foreach my $test (@$tests) { foreach my $test (@$tests) {
my ($t, $expect, $nextsync) = @$test; my ($t, $expect, $nextsync) = @$test;
$expect //= {};
my $timespec; my $timespec;
eval { $timespec = PVE::CalendarEvent::parse_calendar_event($t); }; eval { $timespec = PVE::CalendarEvent::parse_calendar_event($t); };
my $err = $@; my $err = $@;
delete $timespec->{utc};
if ($expect->{error}) { if ($expect->{error}) {
chomp $err if $err; chomp $err if $err;
$timespec = { error => $err } if $err; ok(defined($err) == defined($expect->{error}), "parsing '$t' failed expectedly");
is_deeply($timespec, $expect, "expect parse error on '$t' - $expect->{error}");
die "unable to execute nextsync tests" if $nextsync; die "unable to execute nextsync tests" if $nextsync;
} else {
is_deeply($timespec, $expect, "parse '$t'");
} }
next if !$nextsync; next if !$nextsync;