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());