|
| 1 | +using Cronos; |
| 2 | + |
| 3 | +namespace ClawSharp.Infrastructure.Cron; |
| 4 | + |
| 5 | +/// <summary> |
| 6 | +/// Schedule based on a standard cron expression (5 fields). |
| 7 | +/// Supports minute, hour, day of month, month, and day of week. |
| 8 | +/// </summary> |
| 9 | +public sealed class CronScheduleExpr : ISchedule |
| 10 | +{ |
| 11 | + private readonly CronExpression _expression; |
| 12 | + private readonly string _expressionString; |
| 13 | + private readonly TimeZoneInfo _timeZone; |
| 14 | + |
| 15 | + public ScheduleKind Kind => ScheduleKind.Cron; |
| 16 | + |
| 17 | + /// <summary>The raw cron expression string.</summary> |
| 18 | + public string Expression => _expressionString; |
| 19 | + |
| 20 | + /// <summary> |
| 21 | + /// Creates a cron schedule from the given expression. |
| 22 | + /// </summary> |
| 23 | + /// <param name="expression">Cron expression (5 fields: minute hour day-of-month month day-of-week).</param> |
| 24 | + /// <param name="timeZone">Optional timezone for evaluation. Defaults to UTC.</param> |
| 25 | + /// <exception cref="ArgumentException">Thrown if the expression is invalid.</exception> |
| 26 | + public CronScheduleExpr(string expression, TimeZoneInfo? timeZone = null) |
| 27 | + { |
| 28 | + ArgumentNullException.ThrowIfNull(expression); |
| 29 | + |
| 30 | + try |
| 31 | + { |
| 32 | + _expression = CronExpression.Parse(expression); |
| 33 | + } |
| 34 | + catch (CronFormatException ex) |
| 35 | + { |
| 36 | + throw new ArgumentException($"Invalid cron expression: {expression}", nameof(expression), ex); |
| 37 | + } |
| 38 | + |
| 39 | + _expressionString = expression; |
| 40 | + _timeZone = timeZone ?? TimeZoneInfo.Utc; |
| 41 | + } |
| 42 | + |
| 43 | + public bool IsDue(DateTimeOffset now, DateTimeOffset? lastRun) |
| 44 | + { |
| 45 | + if (lastRun is null) |
| 46 | + return true; |
| 47 | + |
| 48 | + // Get the next occurrence after the last run |
| 49 | + var nextOccurrence = GetNextOccurrence(lastRun.Value); |
| 50 | + |
| 51 | + // It's due if the next occurrence is at or before now |
| 52 | + return nextOccurrence.HasValue && nextOccurrence.Value <= now; |
| 53 | + } |
| 54 | + |
| 55 | + public DateTimeOffset? GetNextOccurrence(DateTimeOffset from) |
| 56 | + { |
| 57 | + var next = _expression.GetNextOccurrence(from.UtcDateTime, _timeZone); |
| 58 | + return next.HasValue ? new DateTimeOffset(next.Value, TimeSpan.Zero) : null; |
| 59 | + } |
| 60 | +} |
0 commit comments