From c8853ecb4fbc1b3b6f1a8cfb197b92f5ce502a24 Mon Sep 17 00:00:00 2001 From: Steve Pelech Date: Sun, 15 Jan 2023 21:29:43 -0600 Subject: [PATCH 1/3] add boolean to specify a cron string with seconds --- .../NetDaemon.Extensions.Scheduling/CronExtensions.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs b/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs index b1db96ba5..1432b2ad7 100644 --- a/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs +++ b/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs @@ -17,15 +17,16 @@ 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); - + CronFormat 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()); From 40cac1f7a35f776e8e4f2f56c9a40222a955b90e Mon Sep 17 00:00:00 2001 From: Steve Pelech Date: Sun, 15 Jan 2023 21:49:18 -0600 Subject: [PATCH 2/3] update TestCron for new format --- .../Scheduling/CronTests.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs b/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs index f3935c646..29f70911f 100644 --- a/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs +++ b/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs @@ -16,6 +16,8 @@ public void TestCron() { var count = 0; var sched = new TestScheduler(); + + #region Standard Format Cron sched.AdvanceTo(DateTime.UtcNow.Ticks); var sub = sched.ScheduleCron("0 * * * *", () => count++); @@ -32,8 +34,30 @@ public void TestCron() sub.Dispose(); sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); count.Should().Be(3, because: "Action should not fire after Dispose()"); + #endregion + + #region Cron with Seconds + sched = new TestScheduler(); + count = 0; + sched.AdvanceTo(DateTime.UtcNow.Ticks); + var subSec = sched.ScheduleCron("*/30 0 * * * *", () => count++,true); + + sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); + count.Should().Be(2); + + sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); + count.Should().Be(4); + + sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); + count.Should().Be(6); + + subSec.Dispose(); + sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); + count.Should().Be(6, because: "Action should not fire after Dispose()"); + #endregion + } - + [Fact] public void ContinueAfterActionThrows() { From 18c6b1b2f23806863ed8945dc34e41bfeb3ee3c0 Mon Sep 17 00:00:00 2001 From: Frank Bakker Date: Mon, 6 Feb 2023 15:38:40 +0100 Subject: [PATCH 3/3] Adjust test --- .../Scheduling/CronTests.cs | 41 ++++++++++++------- .../CronExtensions.cs | 5 ++- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs b/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs index 29f70911f..51739006e 100644 --- a/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs +++ b/src/Extensions/NetDaemon.Extensions.Scheduling.Tests/Scheduling/CronTests.cs @@ -17,7 +17,6 @@ public void TestCron() var count = 0; var sched = new TestScheduler(); - #region Standard Format Cron sched.AdvanceTo(DateTime.UtcNow.Ticks); var sub = sched.ScheduleCron("0 * * * *", () => count++); @@ -27,35 +26,47 @@ public void TestCron() sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); count.Should().Be(2); - + sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); count.Should().Be(3); sub.Dispose(); sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); count.Should().Be(3, because: "Action should not fire after Dispose()"); - #endregion + } + + [Fact] + public void TestCronSeconds() + { + var count = 0; + var sched = new TestScheduler(); - #region Cron with Seconds sched = new TestScheduler(); count = 0; - sched.AdvanceTo(DateTime.UtcNow.Ticks); - var subSec = sched.ScheduleCron("*/30 0 * * * *", () => count++,true); + 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.AdvanceBy(TimeSpan.FromHours(1).Ticks); - count.Should().Be(2); + sched.AdvanceTo(new DateTime(2020, 2, 3, 8, minute: 0, second: 29).Ticks); + count.Should().Be(0); - sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); - count.Should().Be(4); + sched.AdvanceTo(new DateTime(2020, 2, 3, 8, minute: 0, second: 30).Ticks); + count.Should().Be(1); - sched.AdvanceBy(TimeSpan.FromHours(1).Ticks); - count.Should().Be(6); + 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(6, because: "Action should not fire after Dispose()"); - #endregion - + count.Should().Be(3, because: "Action should not fire after Dispose()"); } [Fact] diff --git a/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs b/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs index 1432b2ad7..785ee54d4 100644 --- a/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs +++ b/src/Extensions/NetDaemon.Extensions.Scheduling/CronExtensions.cs @@ -22,11 +22,12 @@ public static class CronExtensions public static IDisposable ScheduleCron(this IScheduler scheduler, string cron, Action action, bool hasSeconds = false) { ArgumentNullException.ThrowIfNull(scheduler); - CronFormat format = hasSeconds ? CronFormat.IncludeSeconds : CronFormat.Standard; + + 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,format), 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());