fix: Streamlines event scheduler API. Adds months to weekly recurrence (#2178)
This commit is contained in:
parent
990e86b983
commit
f95b3a8b3b
11 changed files with 347 additions and 137 deletions
|
|
@ -41,8 +41,8 @@ public class EventSchedulerTests
|
|||
public int CallCount { get; private set; }
|
||||
public Action Callback { get; }
|
||||
|
||||
public TestScheduledEvent(DateTime startAfter, Action callback, IRecurrencePattern recurrence = null)
|
||||
: base(startAfter, TimeOnly.FromDateTime(startAfter), recurrence, TimeZoneInfo.Utc)
|
||||
public TestScheduledEvent(TimeOnly time, Action callback, IRecurrencePattern recurrence = null)
|
||||
: base(time, recurrence)
|
||||
{
|
||||
CallCount = 0;
|
||||
Callback = callback;
|
||||
|
|
@ -105,25 +105,28 @@ public class EventSchedulerTests
|
|||
var executionOrder = new List<int>();
|
||||
|
||||
// Create events with staggered occurrences
|
||||
var evt3 = new TestScheduledEvent(
|
||||
Core._now.AddSeconds(30),
|
||||
() => executionOrder.Add(3)
|
||||
);
|
||||
|
||||
var evt1Start = Core._now.AddSeconds(10);
|
||||
var evt1 = new TestScheduledEvent(
|
||||
Core._now.AddSeconds(10),
|
||||
TimeOnly.FromDateTime(Core._now.AddSeconds(10)),
|
||||
() => executionOrder.Add(1)
|
||||
);
|
||||
|
||||
var evt2Start = Core._now.AddSeconds(20);
|
||||
var evt2 = new TestScheduledEvent(
|
||||
Core._now.AddSeconds(20),
|
||||
TimeOnly.FromDateTime(evt2Start),
|
||||
() => executionOrder.Add(2)
|
||||
);
|
||||
|
||||
var evt3Start = Core._now.AddSeconds(30);
|
||||
var evt3 = new TestScheduledEvent(
|
||||
TimeOnly.FromDateTime(evt3Start),
|
||||
() => executionOrder.Add(3)
|
||||
);
|
||||
|
||||
// Add out of order
|
||||
EventScheduler.Shared.ScheduleEvent(evt3);
|
||||
EventScheduler.Shared.ScheduleEvent(evt1);
|
||||
EventScheduler.Shared.ScheduleEvent(evt2);
|
||||
evt3.Schedule(evt3Start);
|
||||
evt1.Schedule(evt1Start);
|
||||
evt2.Schedule(evt2Start);
|
||||
|
||||
// Advance time to after all events
|
||||
Core._now = Core._now.AddSeconds(40);
|
||||
|
|
@ -157,12 +160,12 @@ public class EventSchedulerTests
|
|||
var recurrence = new TestRecurrencePattern(TimeSpan.FromSeconds(10), 3);
|
||||
|
||||
var evt = new TestScheduledEvent(
|
||||
Core._now,
|
||||
TimeOnly.FromDateTime(Core._now),
|
||||
() => callCount++,
|
||||
recurrence
|
||||
);
|
||||
|
||||
EventScheduler.Shared.ScheduleEvent(evt);
|
||||
evt.Schedule(Core._now);
|
||||
|
||||
// Advance time to after all occurrences should have happened
|
||||
Core._now = Core._now.AddSeconds(50);
|
||||
|
|
@ -271,14 +274,13 @@ public class EventSchedulerTests
|
|||
var endTime = Core._now.AddSeconds(36);
|
||||
|
||||
var customEvent = new CustomEvent(
|
||||
startTime,
|
||||
endTime,
|
||||
TimeOnly.FromDateTime(startTime),
|
||||
endTime,
|
||||
recurrence,
|
||||
() => callCount++
|
||||
);
|
||||
|
||||
EventScheduler.Shared.ScheduleEvent(customEvent);
|
||||
customEvent.Schedule(startTime);
|
||||
|
||||
Core._now = Core._now.AddSeconds(50);
|
||||
Timer.Slice(8);
|
||||
|
|
@ -297,12 +299,11 @@ public class EventSchedulerTests
|
|||
private readonly Action _callback;
|
||||
|
||||
public CustomEvent(
|
||||
DateTime startAfter,
|
||||
DateTime endOn,
|
||||
TimeOnly time,
|
||||
DateTime endOn,
|
||||
IRecurrencePattern recurrence,
|
||||
Action callback
|
||||
) : base(startAfter, endOn, time, recurrence, TimeZoneInfo.Utc) => _callback = callback;
|
||||
) : base(time, endOn, recurrence) => _callback = callback;
|
||||
|
||||
public override void OnEvent()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -51,6 +51,152 @@ public class RecurrencePatternTests
|
|||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2024, 5, 15, 12, 30, 1, AllowedDays.All, 2024, 5, 16, 12, 30)] // Next day (Thursday)
|
||||
[InlineData(2024, 5, 15, 12, 30, 1, AllowedDays.Monday, 2024, 5, 20, 12, 30)] // Next Monday
|
||||
[InlineData(2024, 5, 15, 12, 30, 2, AllowedDays.Wednesday, 2024, 5, 29, 12, 30)] // Biweekly Wednesday
|
||||
public void WeeklyRecurrencePattern_BasicPatterns_ReturnsCorrectDay(
|
||||
int startYear, int startMonth, int startDay, int startHour, int startMinute,
|
||||
int intervalWeeks, AllowedDays allowedDays,
|
||||
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
|
||||
{
|
||||
// Arrange
|
||||
var tz = TimeZoneInfo.Utc;
|
||||
var pattern = new WeeklyRecurrencePattern(intervalWeeks, AllowedMonths.All, allowedDays);
|
||||
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
|
||||
var time = new TimeOnly(expectedHour, expectedMinute);
|
||||
|
||||
// Act
|
||||
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
|
||||
|
||||
// Assert
|
||||
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2024, 12, 30, 12, 30, 1, AllowedDays.Monday, 2025, 1, 6, 12, 30)] // Across year boundary
|
||||
[InlineData(2024, 5, 31, 12, 30, 1, AllowedDays.Saturday, 2024, 6, 1, 12, 30)] // Across month boundary
|
||||
public void WeeklyRecurrencePattern_CrossBoundaries_ReturnsCorrectDay(
|
||||
int startYear, int startMonth, int startDay, int startHour, int startMinute,
|
||||
int intervalWeeks, AllowedDays allowedDays,
|
||||
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
|
||||
{
|
||||
// Arrange
|
||||
var tz = TimeZoneInfo.Utc;
|
||||
var pattern = new WeeklyRecurrencePattern(intervalWeeks, AllowedMonths.All, allowedDays);
|
||||
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
|
||||
var time = new TimeOnly(expectedHour, expectedMinute);
|
||||
|
||||
// Act
|
||||
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
|
||||
|
||||
// Assert
|
||||
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2024, 5, 15, 12, 30, AllowedMonths.January | AllowedMonths.February, AllowedDays.Wednesday, 2025, 1, 1, 12, 30)] // Skip to January
|
||||
[InlineData(2024, 12, 15, 12, 30, AllowedMonths.January | AllowedMonths.December, AllowedDays.Wednesday, 2024, 12, 18, 12, 30)] // Same month
|
||||
[InlineData(2024, 1, 31, 12, 30, AllowedMonths.February | AllowedMonths.April, AllowedDays.Saturday, 2024, 2, 3, 12, 30)] // Next month allowed
|
||||
public void WeeklyRecurrencePattern_MonthFiltering_ReturnsCorrectDay(
|
||||
int startYear, int startMonth, int startDay, int startHour, int startMinute,
|
||||
AllowedMonths allowedMonths, AllowedDays allowedDays,
|
||||
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
|
||||
{
|
||||
// Arrange
|
||||
var tz = TimeZoneInfo.Utc;
|
||||
var pattern = new WeeklyRecurrencePattern(1, allowedMonths, allowedDays);
|
||||
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
|
||||
var time = new TimeOnly(expectedHour, expectedMinute);
|
||||
|
||||
// Act
|
||||
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
|
||||
|
||||
// Assert
|
||||
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("America/New_York", 2024, 3, 9, 12, 0, 1, AllowedDays.Sunday, 2024, 3, 10, 12, 0)] // Before spring forward
|
||||
[InlineData("America/New_York", 2024, 3, 10, 2, 30, 1, AllowedDays.Sunday, 2024, 3, 17, 2, 30)] // During spring forward (invalid time)
|
||||
[InlineData("America/New_York", 2024, 11, 2, 12, 0, 1, AllowedDays.Sunday, 2024, 11, 3, 12, 0)] // Before fall back
|
||||
[InlineData("America/New_York", 2024, 11, 3, 1, 30, 1, AllowedDays.Sunday, 2024, 11, 10, 1, 30)] // During fall back (ambiguous time)
|
||||
public void WeeklyRecurrencePattern_DSTTransitions_HandlesCorrectly(
|
||||
string tzId, int startYear, int startMonth, int startDay, int startHour, int startMinute,
|
||||
int intervalWeeks, AllowedDays allowedDays,
|
||||
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
|
||||
{
|
||||
// Arrange
|
||||
var tz = TimeZoneInfo.FindSystemTimeZoneById(tzId);
|
||||
var pattern = new WeeklyRecurrencePattern(intervalWeeks, AllowedMonths.All, allowedDays);
|
||||
|
||||
var startLocal = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0);
|
||||
var afterUtc = startLocal.LocalToUtc(tz);
|
||||
var time = new TimeOnly(expectedHour, expectedMinute);
|
||||
|
||||
// Act
|
||||
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
|
||||
|
||||
// Assert
|
||||
var resultLocal = TimeZoneInfo.ConvertTimeFromUtc(result, tz);
|
||||
Assert.Equal(new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0), resultLocal);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2024, 5, 29, 12, 0, AllowedDays.None, 2024, 5, 29, 12, 30)] // Default to current day
|
||||
[InlineData(2024, 5, 29, 12, 0, AllowedDays.Wednesday | AllowedDays.Friday, 2024, 5, 29, 12, 30)] // Current day is Wednesday
|
||||
[InlineData(2024, 5, 29, 12, 0, AllowedDays.Monday | AllowedDays.Friday, 2024, 5, 31, 12, 30)] // Next allowed day (Friday)
|
||||
public void WeeklyRecurrencePattern_DaysOfWeekHandling_ReturnsCorrectDay(
|
||||
int startYear, int startMonth, int startDay, int startHour, int startMinute,
|
||||
AllowedDays allowedDays,
|
||||
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
|
||||
{
|
||||
// Arrange
|
||||
var tz = TimeZoneInfo.Utc;
|
||||
var pattern = new WeeklyRecurrencePattern(1, AllowedMonths.All, allowedDays);
|
||||
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
|
||||
var time = new TimeOnly(expectedHour, expectedMinute);
|
||||
|
||||
// Act
|
||||
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
|
||||
|
||||
// Assert
|
||||
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2024, 1, 29, 12, 0, 1, 2024, 1, 29, 12, 30, AllowedMonths.January | AllowedMonths.March, 2024, 1, 30, 12, 30)] // Current month allowed
|
||||
[InlineData(2024, 1, 31, 12, 0, 1, 2024, 1, 31, 12, 30, AllowedMonths.January | AllowedMonths.March, 2024, 3, 1, 12, 30)] // Skip February
|
||||
public void WeeklyRecurrencePattern_WeekSpanningMonths_ReturnsCorrectDay(
|
||||
int startYear, int startMonth, int startDay, int startHour, int startMinute,
|
||||
int intervalWeeks,
|
||||
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute,
|
||||
AllowedMonths allowedMonths,
|
||||
int nextExpectedYear, int nextExpectedMonth, int nextExpectedDay, int nextExpectedHour, int nextExpectedMinute)
|
||||
{
|
||||
// Arrange
|
||||
var tz = TimeZoneInfo.Utc;
|
||||
var pattern = new WeeklyRecurrencePattern(intervalWeeks, allowedMonths);
|
||||
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
|
||||
var time = new TimeOnly(expectedHour, expectedMinute);
|
||||
|
||||
// Act - first call should hit expectedDate
|
||||
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
|
||||
|
||||
// Assert
|
||||
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
|
||||
Assert.Equal(expected, result);
|
||||
|
||||
// Call again - should move to next available day in allowed months
|
||||
var nextResult = pattern.GetNextOccurrence(result, time, tz);
|
||||
var nextExpected = new DateTime(nextExpectedYear, nextExpectedMonth, nextExpectedDay, nextExpectedHour, nextExpectedMinute, 0, DateTimeKind.Utc);
|
||||
Assert.Equal(nextExpected, nextResult);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2024, 11, 3, 1, 30, DayOfWeek.Sunday, OrdinalDayOccurrence.First, "America/New_York", 2024, 11, 3)]
|
||||
[InlineData(2024, 3, 31, 0, 0, DayOfWeek.Sunday, OrdinalDayOccurrence.Last, "America/New_York", 2024, 3, 31)]
|
||||
|
|
@ -145,7 +291,6 @@ public class RecurrencePatternTests
|
|||
Assert.Equal(new DateTime(2024, 3, 31, 12, 0, 0), result);
|
||||
}
|
||||
|
||||
// Additional utility method tests
|
||||
[Theory]
|
||||
[InlineData("America/New_York", 2024, 11, 3, 1, 30)] // Fall back - ambiguous time
|
||||
public void LocalToUtc_AmbiguousTime_HandlesConsistently(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue