Cron: Bereich mit Schritt (7-22/3) lesen, ungueltige Ausdruecke im Scanner melden (#597)

Der Nachrichten-Digest im Dauerbetrieb der Test-VM lief nie: CronExpression
verstand 'von-bis/schritt' nicht, TaskSchedule verschluckte den Fehler.
Version 0.1.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-14 15:51:36 +02:00
co-authored by Claude Opus 5
parent 6581fd0187
commit 8ab9d99abe
6 changed files with 118 additions and 13 deletions
+13
View File
@@ -52,6 +52,9 @@ public sealed class TaskScanner : IAsyncDisposable
/// läuft jede Minute, die Meldung soll nicht mitlaufen.</summary>
private readonly HashSet<string> _warnedTimeZones = [];
/// <summary>Aufgaben, deren ungültiger Cron-Ausdruck bereits gemeldet wurde.</summary>
private readonly HashSet<string> _warnedCron = [];
private readonly CancellationTokenSource _cts = new();
private Task? _loop;
@@ -173,6 +176,16 @@ public sealed class TaskScanner : IAsyncDisposable
task.Id, task.Title, badZone);
}
// Dasselbe für einen Cron-Ausdruck, den der Parser nicht versteht (#597).
if (TaskSchedule.InvalidCron(task) is { } cronError
&& _warnedCron.Add(task.Id))
{
_logger.LogWarning(
"Aufgabe {TaskId} ({Title}) hat den Termin cron \"{Cron}\", der nicht lesbar ist "
+ "— sie wird nicht ausgeführt. {Fehler}",
task.Id, task.Title, task.When!.Value, cronError);
}
var occ = TaskSchedule.DueOccurrence(task, now);
if (occ is null)
continue;
@@ -60,6 +60,29 @@ public static class TaskSchedule
? when.TimeZone
: null;
/// <summary>
/// Der Parserfehler eines Cron-Termins, wenn der Ausdruck ungültig ist — sonst
/// <c>null</c>.
///
/// Ein solcher Termin feuert nie (<c>DueCron</c> fängt den Fehler ab, damit ein
/// kaputter Ausdruck nicht den ganzen Takt kippt). Wie bei der Zeitzone meldet der
/// Scanner das einmal je Aufgabe — vorher stand die Aufgabe als <c>todo</c> mit Termin
/// im Taskboard und lief schlicht nie (#597).
/// </summary>
public static string? InvalidCron(TaskItem task)
{
if (task.When is not { Kind: TaskWhenKind.Cron } when) return null;
try
{
CronExpression.Parse(when.Value);
return null;
}
catch (FormatException ex)
{
return ex.Message;
}
}
private static string? DueAt(TaskWhen when, string? marker, DateTime nowUtc)
{
if (marker is not null) return null; // ein einmaliger Termin feuert genau einmal