diff --git a/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs b/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs index f3935c646..51739006e 100644 --- a/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs +++ b/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs @@ -16,6 +16,7 @@ public void TestCron() { var count = 0; var sched = new TestScheduler(); + sched.AdvanceTo(DateTime.UtcNow.Ticks); var sub = sched.ScheduleCron("0 * * * *", () => count++); @@ -25,7 +26,7 @@ public void TestCron() sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); count.Should().Be(2); - + sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); count.Should().Be(3); @@ -33,7 +34,41 @@ public void TestCron() sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); count.Should().Be(3, because: "Action should not fire after Dispose()"); } - + + [Fact] + public void TestCronSeconds() + { + var count = 0; + var sched = new TestScheduler(); + + sched = new TestScheduler(); + count = 0; + sched.AdvanceTo(new DateTime(2020, 2, 3, 8, 0, 28).Ticks); + var subSec = sched.ScheduleCron("*/30 0 * * * *", () => count++, hasSeconds: true); // 0 and 30 seconds after every whole hour + + sched.AdvanceTo(new DateTime(2020, 2, 3, 8, minute: 0, second: 29).Ticks); + count.Should().Be(0); + + sched.AdvanceTo(new DateTime(2020, 2, 3, 8, minute: 0, second: 30).Ticks); + count.Should().Be(1); + + sched.AdvanceTo(new DateTime(2020, 2, 3, 8, minute: 1, second: 1).Ticks); + count.Should().Be(1); + + sched.AdvanceBy(TimeSpan.FromMinutes(10).Ticks); + count.Should().Be(1); + + sched.AdvanceTo(new DateTime(2020, 2, 3, 9, minute: 0, second: 0).Ticks); + count.Should().Be(2); + + sched.AdvanceTo(new DateTime(2020, 2, 3, 9, minute: 0, second: 30).Ticks); + count.Should().Be(3); + + subSec.Dispose(); + sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); + count.Should().Be(3, because: "Action should not fire after Dispose()"); + } + [Fact] public void ContinueAfterActionThrows() { diff --git a/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs b/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs index b1db96ba5..785ee54d4 100644 --- a/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs +++ b/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs @@ -17,15 +17,17 @@ public static class CronExtensions /// IScheduler to use for this action /// Cron expression that describes the schedule /// Callback to execute + /// True if cron has Seconds specified /// Disposable object that allows the schedule to be cancelled - public static IDisposable ScheduleCron(this IScheduler scheduler, string cron, Action action) + public static IDisposable ScheduleCron(this IScheduler scheduler, string cron, Action action, bool hasSeconds = false) { ArgumentNullException.ThrowIfNull(scheduler); - + + var format = hasSeconds ? CronFormat.IncludeSeconds : CronFormat.Standard; // When this gets cancelled we only need to actually dispose of the most recent scheduled action // (there will only be one at a time) so we store that in a box we will pass down StrongBox disposableBox = new(); - RecursiveSchedule(scheduler, CronExpression.Parse(cron), action, disposableBox, scheduler.Now); + RecursiveSchedule(scheduler, CronExpression.Parse(cron, format), action, disposableBox, scheduler.Now); // Dispose will Dispose the IDisposable in the box return Disposable.Create(()=> disposableBox.Value?.Dispose());