Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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++);
Expand All @@ -25,15 +26,49 @@ 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()");
}


[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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ public static class CronExtensions
/// <param name="scheduler">IScheduler to use for this action</param>
/// <param name="cron">Cron expression that describes the schedule</param>
/// <param name="action">Callback to execute</param>
/// <param name="hasSeconds">True if cron has Seconds specified</param>
/// <returns>Disposable object that allows the schedule to be cancelled</returns>
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<IDisposable?> 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());
Expand Down