Initial commit: ClawdDotNet
Import des bestehenden Projektstands in Git. - .NET 10 WinForms Anwendung (Multi-Agent / Tool-System) - .gitignore fuer Build-Artefakte, Secrets und Runtime-Daten ergaenzt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
namespace ClawdDotNet.Core.Scheduling;
|
||||
|
||||
/// <summary>
|
||||
/// Einfaches Cron-Parsing für 5-Felder-Ausdrücke: Minute Stunde Tag Monat Wochentag
|
||||
/// Unterstützt: Zahlen, Wildcards (*), Bereiche (1-5), Listen (1,3,5), Schritte (*/5)
|
||||
/// </summary>
|
||||
public sealed class CronExpression
|
||||
{
|
||||
private readonly HashSet<int> _minutes;
|
||||
private readonly HashSet<int> _hours;
|
||||
private readonly HashSet<int> _daysOfMonth;
|
||||
private readonly HashSet<int> _months;
|
||||
private readonly HashSet<int> _daysOfWeek;
|
||||
|
||||
private CronExpression(
|
||||
HashSet<int> minutes, HashSet<int> hours,
|
||||
HashSet<int> daysOfMonth, HashSet<int> months,
|
||||
HashSet<int> daysOfWeek)
|
||||
{
|
||||
_minutes = minutes;
|
||||
_hours = hours;
|
||||
_daysOfMonth = daysOfMonth;
|
||||
_months = months;
|
||||
_daysOfWeek = daysOfWeek;
|
||||
}
|
||||
|
||||
public static CronExpression Parse(string expression)
|
||||
{
|
||||
var parts = expression.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 5)
|
||||
throw new FormatException($"Cron expression must have 5 fields, got {parts.Length}: '{expression}'");
|
||||
|
||||
return new CronExpression(
|
||||
ParseField(parts[0], 0, 59),
|
||||
ParseField(parts[1], 0, 23),
|
||||
ParseField(parts[2], 1, 31),
|
||||
ParseField(parts[3], 1, 12),
|
||||
ParseField(parts[4], 0, 6)
|
||||
);
|
||||
}
|
||||
|
||||
public bool Matches(DateTime dt)
|
||||
{
|
||||
return _minutes.Contains(dt.Minute)
|
||||
&& _hours.Contains(dt.Hour)
|
||||
&& _daysOfMonth.Contains(dt.Day)
|
||||
&& _months.Contains(dt.Month)
|
||||
&& _daysOfWeek.Contains((int)dt.DayOfWeek);
|
||||
}
|
||||
|
||||
public DateTime? GetNextOccurrence(DateTime after)
|
||||
{
|
||||
var candidate = new DateTime(after.Year, after.Month, after.Day, after.Hour, after.Minute, 0)
|
||||
.AddMinutes(1);
|
||||
|
||||
// Suche maximal 2 Jahre in die Zukunft
|
||||
var limit = after.AddYears(2);
|
||||
|
||||
while (candidate < limit)
|
||||
{
|
||||
if (Matches(candidate))
|
||||
return candidate;
|
||||
|
||||
candidate = candidate.AddMinutes(1);
|
||||
|
||||
// Optimierung: überspringe ungültige Stunden/Tage
|
||||
if (!_months.Contains(candidate.Month))
|
||||
{
|
||||
candidate = new DateTime(candidate.Year, candidate.Month, 1).AddMonths(1);
|
||||
continue;
|
||||
}
|
||||
if (!_daysOfMonth.Contains(candidate.Day) || !_daysOfWeek.Contains((int)candidate.DayOfWeek))
|
||||
{
|
||||
candidate = new DateTime(candidate.Year, candidate.Month, candidate.Day).AddDays(1);
|
||||
continue;
|
||||
}
|
||||
if (!_hours.Contains(candidate.Hour))
|
||||
{
|
||||
candidate = new DateTime(candidate.Year, candidate.Month, candidate.Day, candidate.Hour, 0, 0)
|
||||
.AddHours(1);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HashSet<int> ParseField(string field, int min, int max)
|
||||
{
|
||||
var result = new HashSet<int>();
|
||||
|
||||
foreach (var part in field.Split(','))
|
||||
{
|
||||
if (part == "*")
|
||||
{
|
||||
for (var i = min; i <= max; i++) result.Add(i);
|
||||
}
|
||||
else if (part.Contains('/'))
|
||||
{
|
||||
var split = part.Split('/');
|
||||
var start = split[0] == "*" ? min : int.Parse(split[0]);
|
||||
var step = int.Parse(split[1]);
|
||||
for (var i = start; i <= max; i += step) result.Add(i);
|
||||
}
|
||||
else if (part.Contains('-'))
|
||||
{
|
||||
var split = part.Split('-');
|
||||
var from = int.Parse(split[0]);
|
||||
var to = int.Parse(split[1]);
|
||||
for (var i = from; i <= to; i++) result.Add(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(int.Parse(part));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user