diff --git a/src/FubuTransportation.Testing/ScheduledJobs/EveryDayAtSpecificTimeJobSchedulerTester.cs b/src/FubuTransportation.Testing/ScheduledJobs/EveryDayAtSpecificTimeJobSchedulerTester.cs index b255ff2..ee6125d 100644 --- a/src/FubuTransportation.Testing/ScheduledJobs/EveryDayAtSpecificTimeJobSchedulerTester.cs +++ b/src/FubuTransportation.Testing/ScheduledJobs/EveryDayAtSpecificTimeJobSchedulerTester.cs @@ -1,7 +1,7 @@ using System; using FubuTestingSupport; -using FubuTransportation.ScheduledJobs; using FubuTransportation.ScheduledJobs.Execution; +using FubuTransportation.ScheduledJobs.Persistence; using NUnit.Framework; namespace FubuTransportation.Testing.ScheduledJobs @@ -56,6 +56,82 @@ protected override void beforeEach() nextScheduledTime = ClassUnderTest.ScheduleNextTime(LocalSystemTime, null); } + [Test] + public void next_scheduled_time_should_be_right_now() + { + nextScheduledTime.LocalDateTime.ShouldEqual(DateTime.Today.AddHours(7).AddMinutes(33)); // Today 7:33am + } + } + + [TestFixture] + public class when_scheduling_every_day_past_specified_time_within_grace_period_and_no_last_execution : InteractionContext + { + private DateTimeOffset nextScheduledTime; + + protected override void beforeEach() + { + Container.Inject(new EveryDayAtSpecificTime(hour: 07, minute: 33)); // 7:33am + LocalSystemTime = DateTime.Today.AddHours(7).AddMinutes(37); // 7:37am + nextScheduledTime = ClassUnderTest.ScheduleNextTime(LocalSystemTime, null); + } + + [Test] + public void next_scheduled_time_should_be_slightly_in_past() + { + nextScheduledTime.LocalDateTime.ShouldEqual(DateTime.Today.AddHours(7).AddMinutes(33)); // Today 7:33am + } + } + + [TestFixture] + public class when_scheduling_every_day_past_specified_time_outside_grace_period : InteractionContext + { + private DateTimeOffset nextScheduledTime; + + protected override void beforeEach() + { + Container.Inject(new EveryDayAtSpecificTime(hour: 07, minute: 33)); // 7:33am + LocalSystemTime = DateTime.Today.AddHours(7).AddMinutes(53); // 7:53am + nextScheduledTime = ClassUnderTest.ScheduleNextTime(LocalSystemTime, null); + } + + [Test] + public void next_scheduled_time_should_start_the_next_day() + { + nextScheduledTime.LocalDateTime.ShouldEqual(DateTime.Today.AddDays(1).AddHours(7).AddMinutes(33)); // Tomorrow 7:33am + } + } + + [TestFixture] + public class when_scheduling_every_day_past_specified_time_within_grace_period_and_last_execution_not_within_grace_period : InteractionContext + { + private DateTimeOffset nextScheduledTime; + + protected override void beforeEach() + { + Container.Inject(new EveryDayAtSpecificTime(hour: 07, minute: 33)); // 7:33am + LocalSystemTime = DateTime.Today.AddHours(7).AddMinutes(37); // 7:37am + nextScheduledTime = ClassUnderTest.ScheduleNextTime(LocalSystemTime, new JobExecutionRecord { Finished = LocalSystemTime.AddHours(-3) }); // 4:37 am + } + + [Test] + public void next_scheduled_time_should_be_slightly_in_past() + { + nextScheduledTime.LocalDateTime.ShouldEqual(DateTime.Today.AddHours(7).AddMinutes(33)); // Today 7:33am + } + } + + [TestFixture] + public class when_scheduling_every_day_past_specified_time_within_grace_period_and_last_execution_within_grace_period : InteractionContext + { + private DateTimeOffset nextScheduledTime; + + protected override void beforeEach() + { + Container.Inject(new EveryDayAtSpecificTime(hour: 07, minute: 33)); // 7:33am + LocalSystemTime = DateTime.Today.AddHours(7).AddMinutes(37); // 7:37am + nextScheduledTime = ClassUnderTest.ScheduleNextTime(LocalSystemTime, new JobExecutionRecord { Finished = LocalSystemTime.AddMinutes(-2) }); // 7:35 am + } + [Test] public void next_scheduled_time_should_start_the_next_day() { diff --git a/src/FubuTransportation/ScheduledJobs/Execution/EveryDayAtSpecificTime.cs b/src/FubuTransportation/ScheduledJobs/Execution/EveryDayAtSpecificTime.cs index 76a9bb3..4e15b79 100644 --- a/src/FubuTransportation/ScheduledJobs/Execution/EveryDayAtSpecificTime.cs +++ b/src/FubuTransportation/ScheduledJobs/Execution/EveryDayAtSpecificTime.cs @@ -3,27 +3,40 @@ namespace FubuTransportation.ScheduledJobs.Execution { + // This won't work very well with nodes in different time zones public class EveryDayAtSpecificTime : IScheduleRule { private readonly int _hour; private readonly int _minute; + private readonly int _gracePeriodInMinutes; - public EveryDayAtSpecificTime(int hour, int minute) + public EveryDayAtSpecificTime(int hour, int minute, int gracePeriodInMinutes = 15) { _hour = hour; _minute = minute; + _gracePeriodInMinutes = gracePeriodInMinutes; } public DateTimeOffset ScheduleNextTime(DateTimeOffset currentTime, JobExecutionRecord lastExecution) { - var localTime = currentTime.ToLocalTime(); - var oneAmToday = new DateTime(localTime.Year, localTime.Month, localTime.Day, _hour, _minute, 0, 0, DateTimeKind.Local); - var nextScheduledTime = oneAmToday; + var localcurrentTime = currentTime.ToLocalTime(); + var nextScheduledTime = new DateTime(localcurrentTime.Year, localcurrentTime.Month, localcurrentTime.Day, _hour, _minute, 0, 0, DateTimeKind.Local); + var gracePeriodTime = nextScheduledTime.AddMinutes(_gracePeriodInMinutes); - if (localTime.Hour > _hour || (localTime.Hour == _hour && localTime.Minute >= _minute)) + var scheduledTimeHasAlreadyPastToday = nextScheduledTime < localcurrentTime; + var withinGracePeriodRightNow = nextScheduledTime < localcurrentTime && localcurrentTime <= gracePeriodTime; + + var lastExecutionWithinGracePeriod = false; + if (lastExecution != null) + { + var lastExecutionLocalTime = lastExecution.Finished.ToLocalTime(); + lastExecutionWithinGracePeriod = nextScheduledTime < lastExecutionLocalTime && lastExecutionLocalTime <= gracePeriodTime; + } + + if (scheduledTimeHasAlreadyPastToday && (!withinGracePeriodRightNow || lastExecutionWithinGracePeriod)) { // Switch to tomorrow - nextScheduledTime = oneAmToday.AddDays(1); + nextScheduledTime = nextScheduledTime.AddDays(1); } return nextScheduledTime.ToUniversalTime(); @@ -37,4 +50,4 @@ public EveryDayAt1Am() : base(hour: 01, minute: 00) {} } */ -} \ No newline at end of file +}