Also fixes PrimeScheduleRepository.ListAsync to sort client-side (SQLite EF Core does not support TimeSpan in ORDER BY clauses).
70 lines
2.4 KiB
C#
70 lines
2.4 KiB
C#
using ClaudeDo.Data.Models;
|
|
|
|
namespace ClaudeDo.Worker.Prime;
|
|
|
|
public sealed record NextDue(PrimeScheduleDto Schedule, DateTimeOffset At, bool FireImmediately);
|
|
|
|
public static class NextDueCalculator
|
|
{
|
|
public static NextDue? Compute(
|
|
IEnumerable<PrimeScheduleDto> schedules,
|
|
DateTimeOffset now,
|
|
TimeSpan catchUp)
|
|
{
|
|
NextDue? best = null;
|
|
foreach (var s in schedules)
|
|
{
|
|
if (!s.Enabled) continue;
|
|
var due = ComputeFor(s, now, catchUp);
|
|
if (due is null) continue;
|
|
if (best is null || due.At < best.At) best = due;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
private static NextDue? ComputeFor(PrimeScheduleDto s, DateTimeOffset now, TimeSpan catchUp)
|
|
{
|
|
if ((PrimeDays)s.Days == PrimeDays.None) return null;
|
|
|
|
var todayLocal = DateOnly.FromDateTime(now.LocalDateTime);
|
|
var alreadyFiredToday = s.LastRunAt is { } last &&
|
|
DateOnly.FromDateTime(last.LocalDateTime) == todayLocal;
|
|
|
|
if (!alreadyFiredToday && IsEligibleDay(s, todayLocal))
|
|
{
|
|
var todayTarget = ToOffset(todayLocal, s.TimeOfDay, now.Offset);
|
|
if (todayTarget >= now)
|
|
return new NextDue(s, todayTarget, false);
|
|
if (now <= todayTarget + catchUp)
|
|
return new NextDue(s, now, true);
|
|
}
|
|
|
|
var d = todayLocal.AddDays(1);
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
if (IsEligibleDay(s, d))
|
|
return new NextDue(s, ToOffset(d, s.TimeOfDay, now.Offset), false);
|
|
d = d.AddDays(1);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static bool IsEligibleDay(PrimeScheduleDto s, DateOnly d) =>
|
|
((PrimeDays)s.Days & ToFlag(d.DayOfWeek)) != PrimeDays.None;
|
|
|
|
private static PrimeDays ToFlag(DayOfWeek dow) => dow switch
|
|
{
|
|
DayOfWeek.Monday => PrimeDays.Monday,
|
|
DayOfWeek.Tuesday => PrimeDays.Tuesday,
|
|
DayOfWeek.Wednesday => PrimeDays.Wednesday,
|
|
DayOfWeek.Thursday => PrimeDays.Thursday,
|
|
DayOfWeek.Friday => PrimeDays.Friday,
|
|
DayOfWeek.Saturday => PrimeDays.Saturday,
|
|
DayOfWeek.Sunday => PrimeDays.Sunday,
|
|
_ => PrimeDays.None,
|
|
};
|
|
|
|
private static DateTimeOffset ToOffset(DateOnly day, TimeSpan time, TimeSpan offset) =>
|
|
new(day.Year, day.Month, day.Day, time.Hours, time.Minutes, time.Seconds, offset);
|
|
}
|