]> git.proxmox.com Git - pve-common.git/commitdiff
CalendarEvent: use rust implementation
authorDominik Csapak <d.csapak@proxmox.com>
Wed, 1 Dec 2021 08:55:14 +0000 (09:55 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Thu, 13 Jan 2022 13:50:47 +0000 (14:50 +0100)
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>
src/PVE/CalendarEvent.pm
test/calendar_event_test.pl

index 56e992330003c445efe614c62c8154845225bed0..e2bf53ad5b2d1561a8f6e118c3c0238e0140b071 100644 (file)
@@ -6,6 +6,7 @@ use Data::Dumper;
 use Time::Local;
 use PVE::JSONSchema;
 use PVE::Tools qw(trim);
+use PVE::RS::CalendarEvent;
 
 # Note: This class implements a parser/utils for systemd like calendar exents
 # Date specification is currently not implemented
@@ -43,259 +44,13 @@ sub parse_calendar_event {
        die "unable to parse calendar event - event is empty\n";
     }
 
-    my $parse_single_timespec = sub {
-       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]);
+    return PVE::RS::CalendarEvent->new($event);
 }
 
 sub compute_next_event {
     my ($calspec, $last) = @_;
 
-    my $hspec = $calspec->{h};
-    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";
+    return $calspec->compute_next_event($last);
 }
 
 1;
index abbd74cce8632f0a67595605da87c5c658ceb891..457296534ede6b153a29870c6c47f3ac9bdad7e2 100755 (executable)
@@ -18,7 +18,7 @@ my $alldays = [0,1,2,3,4,5,6];
 my $tests = [
     [
      '*',
-     { h => '*', m => '*', dow => $alldays },
+     undef,
      [
       [0, 60],
       [30, 60],
@@ -28,7 +28,7 @@ my $tests = [
     ],
     [
      '*/10',
-     { h => '*', m => [0, 10, 20, 30, 40, 50], dow => $alldays },
+     undef,
      [
       [0, 600],
       [599, 600],
@@ -38,7 +38,7 @@ my $tests = [
     ],
     [
      '*/12:0' ,
-     { h => [0, 12], m => [0], dow => $alldays },
+     undef,
      [
       [ 10, 43200],
       [ 13*3600, 24*3600],
@@ -46,7 +46,7 @@ my $tests = [
     ],
     [
      '1/12:0/15' ,
-     { h => [1, 13], m => [0, 15, 30, 45], dow => $alldays },
+     undef,
      [
       [0, 3600],
       [3600, 3600+15*60],
@@ -61,7 +61,7 @@ my $tests = [
     ],
     [
      '1,4,6',
-     { h => '*', m => [1, 4, 6], dow => $alldays},
+     undef,
      [
       [0, 60],
       [60, 4*60],
@@ -71,15 +71,15 @@ my $tests = [
     ],
     [
      '0..3',
-     { h => '*', m => [ 0, 1, 2, 3 ], dow => $alldays },
+     undef,
     ],
     [
      '23..23:0..3',
-     { h => [ 23 ], m => [ 0, 1, 2, 3 ], dow => $alldays },
+     undef,
     ],
     [
      'Mon',
-     { h => [0], m => [0], dow => [1] },
+     undef,
      [
       [0, 4*86400], # Note: Epoch 0 is Thursday, 1. January 1970
       [4*86400, 11*86400],
@@ -88,7 +88,7 @@ my $tests = [
     ],
     [
      'sat..sun',
-     { h => [0], m => [0], dow => [0, 6] },
+     undef,
      [
       [0, 2*86400],
       [2*86400, 3*86400],
@@ -97,7 +97,7 @@ my $tests = [
     ],
     [
      'sun..sat',
-     { h => [0], m => [0], dow => $alldays },
+     undef,
     ],
     [
      'Fri..Mon',
@@ -105,15 +105,15 @@ my $tests = [
     ],
     [
      'wed,mon..tue,fri',
-     { h => [0], m => [0], dow => [ 1, 2, 3, 5] },
+     undef,
     ],
     [
      'mon */15',
-     { h => '*', m =>  [0, 15, 30, 45], dow => [1]},
+     undef,
     ],
     [
     '22/1:0',
-     { h => [22, 23], m => [0], dow => $alldays },
+    undef,
      [
        [0, 22*60*60],
        [22*60*60, 23*60*60],
@@ -122,7 +122,7 @@ my $tests = [
     ],
     [
      '*/2:*',
-     { h => [0,2,4,6,8,10,12,14,16,18,20,22], m => '*', dow => $alldays },
+     undef,
      [
        [0, 60],
        [60*60, 2*60*60],
@@ -131,7 +131,7 @@ my $tests = [
     ],
     [
      '20..22:*/30',
-     { h => [20,21,22], m => [0,30], dow => $alldays },
+     undef,
      [
        [0, 20*60*60],
        [20*60*60, 20*60*60 + 30*60],
@@ -164,7 +164,7 @@ my $tests = [
     ],
     [
      '0,1,3..5',
-     { h => '*', m => [0,1,3,4,5], dow => $alldays },
+     undef,
      [
        [0, 60],
        [60, 3*60],
@@ -173,7 +173,7 @@ my $tests = [
     ],
     [
      '2,4:0,1,3..5',
-     { h => [2,4], m => [0,1,3,4,5], dow => $alldays },
+     undef,
      [
        [0, 2*60*60],
        [2*60*60 + 60, 2*60*60 + 3*60],
@@ -185,18 +185,16 @@ my $tests = [
 foreach my $test (@$tests) {
     my ($t, $expect, $nextsync) = @$test;
 
+    $expect //= {};
+
     my $timespec;
     eval { $timespec = PVE::CalendarEvent::parse_calendar_event($t); };
     my $err = $@;
-    delete $timespec->{utc};
 
     if ($expect->{error}) {
        chomp $err if $err;
-       $timespec = { error => $err } if $err;
-       is_deeply($timespec, $expect, "expect parse error on '$t' - $expect->{error}");
+       ok(defined($err) == defined($expect->{error}), "parsing '$t' failed expectedly");
        die "unable to execute nextsync tests" if $nextsync;
-    } else {
-       is_deeply($timespec, $expect, "parse '$t'");
     }
 
     next if !$nextsync;