Vali-Schedule
IValiSchedule es un constructor fluido para horarios recurrentes. Admite patrones de recurrencia diarios, semanales, mensuales y anuales con fecha de inicio, condiciones de fin y predicados personalizados.
Instalación
dotnet add package Vali-Schedule
Registro
builder.Services.AddValiSchedule();
// o mediante el meta-paquete:
builder.Services.AddValiTempo();
IValiSchedule no es seguro para hilos y no debe registrarse como singleton. Regístralo como scoped o transient, o crea nuevas instancias según sea necesario. El patrón del constructor mantiene estado entre llamadas.
Enumeraciones
RecurrenceType
Define con qué frecuencia se repite el horario:
| Valor | Descripción |
|---|---|
Daily | Se repite cada N días |
Weekly | Se repite cada N semanas en días especificados |
Monthly | Se repite cada N meses en un día especificado |
Yearly | Se repite cada N años en un mes y día especificados |
RecurrenceEnd
Define cuándo se detiene el horario:
| Valor | Descripción |
|---|---|
Never | El horario se repite indefinidamente |
AfterOccurrences | Se detiene después de un número fijo de ocurrencias |
OnDate | Se detiene en o después de una fecha de fin específica |
API de constructor fluido de IValiSchedule
Every
Establece el intervalo y el tipo de recurrencia:
schedule.Every(1, RecurrenceType.Daily) // cada día
schedule.Every(2, RecurrenceType.Weekly) // cada 2 semanas
schedule.Every(1, RecurrenceType.Monthly) // cada mes
schedule.Every(1, RecurrenceType.Yearly) // cada año
Firma: IValiSchedule Every(int interval, RecurrenceType type)
On
Para horarios semanales: especifica en qué días de la semana incluir:
schedule.Every(1, RecurrenceType.Weekly)
.On(DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday)
Firma: IValiSchedule On(params DayOfWeek[] days)
At
Establece la hora del día para las ocurrencias:
schedule.Every(1, RecurrenceType.Daily)
.At(9, 0) // 9:00 AM
Firma: IValiSchedule At(int hour, int minute = 0, int second = 0)
StartingFrom
Establece la fecha de inicio (por defecto es DateTime.Today):
schedule.Every(1, RecurrenceType.Weekly)
.StartingFrom(new DateTime(2025, 4, 1))
Firma: IValiSchedule StartingFrom(DateTime startDate)
EndsAfter
Detiene el horario después de un número fijo de ocurrencias:
schedule.Every(1, RecurrenceType.Daily)
.EndsAfter(10) // 10 ocurrencias y luego se detiene
Firma: IValiSchedule EndsAfter(int occurrences)
EndsOn
Detiene el horario en o después de una fecha específica:
schedule.Every(1, RecurrenceType.Monthly)
.EndsOn(new DateTime(2026, 12, 31))
Firma: IValiSchedule EndsOn(DateTime endDate)
OnDayOfMonth
Para horarios mensuales: especifica qué día del mes:
schedule.Every(1, RecurrenceType.Monthly)
.OnDayOfMonth(15) // día 15 de cada mes
Firma: IValiSchedule OnDayOfMonth(int day)
WithCustomPredicate
Agrega un filtro personalizado — solo se incluyen las ocurrencias donde el predicado devuelve true:
schedule.Every(1, RecurrenceType.Daily)
.WithCustomPredicate(date => date.DayOfWeek != DayOfWeek.Friday)
// Omite los viernes
Firma: IValiSchedule WithCustomPredicate(Func<DateTime, bool> predicate)
Build
Finaliza el constructor y devuelve un objeto Schedule de solo lectura:
var sched = schedule
.Every(1, RecurrenceType.Weekly)
.On(DayOfWeek.Monday)
.At(9, 0)
.StartingFrom(new DateTime(2025, 1, 6))
.Build();
Firma: Schedule Build()
Métodos de consulta
Una vez que tienes un Schedule, usa estos métodos para consultar ocurrencias:
NextOccurrence
Devuelve la próxima ocurrencia después de una fecha dada:
DateTime? next = sched.NextOccurrence(DateTime.Now);
// → null si el horario ha terminado
Firma: DateTime? NextOccurrence(DateTime after)
PreviousOccurrence
Devuelve la ocurrencia más reciente antes de una fecha dada:
DateTime? prev = sched.PreviousOccurrence(DateTime.Now);
Firma: DateTime? PreviousOccurrence(DateTime before)
OccursOn
Devuelve true si el horario tiene una ocurrencia en la fecha dada (ignorando la hora):
bool occurs = sched.OccursOn(new DateTime(2025, 4, 7));
Firma: bool OccursOn(DateTime date)
Occurrences
Enumera las próximas N ocurrencias desde una fecha dada:
IEnumerable<DateTime> next5 = sched.Occurrences(5, DateTime.Now);
foreach (var occ in next5)
Console.WriteLine(occ.ToString("g"));
Firma: IEnumerable<DateTime> Occurrences(int count, DateTime from)
OccurrencesInRange
Enumera todas las ocurrencias dentro de un rango de fechas:
var range = new DateRange(new DateTime(2025, 4, 1), new DateTime(2025, 6, 30));
IEnumerable<DateTime> q2Meetings = sched.OccurrencesInRange(range);
Firma: IEnumerable<DateTime> OccurrencesInRange(DateRange range)
Ejemplos completos
Standup semanal
public class StandupScheduler(IValiSchedule schedule)
{
public Schedule BuildStandup(DateTime startDate)
{
return schedule
.Every(1, RecurrenceType.Weekly)
.On(DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday,
DayOfWeek.Thursday, DayOfWeek.Friday)
.At(9, 30)
.StartingFrom(startDate)
.Build();
}
public void PrintNextStandups(Schedule sched, int count = 5)
{
Console.WriteLine($"Next {count} standups:");
foreach (var occ in sched.Occurrences(count, DateTime.Now))
Console.WriteLine($" {occ:ddd, MMM dd HH:mm}");
}
}
Ejecución mensual de nómina
public class PayrollScheduler(IValiSchedule schedule)
{
public Schedule BuildPayroll(int dayOfMonth = 25)
{
return schedule
.Every(1, RecurrenceType.Monthly)
.OnDayOfMonth(dayOfMonth)
.At(8, 0)
.StartingFrom(new DateTime(2025, 1, 1))
// Omitir meses donde el día 25 cae en fin de semana
.WithCustomPredicate(d =>
d.DayOfWeek != DayOfWeek.Saturday &&
d.DayOfWeek != DayOfWeek.Sunday)
.Build();
}
}
Recordatorio anual con condición de fin
var reminder = schedule
.Every(1, RecurrenceType.Yearly)
.At(10, 0)
.StartingFrom(new DateTime(2025, 3, 23))
.EndsAfter(5) // solo 5 años
.Build();
DateTime? nextReminder = reminder.NextOccurrence(DateTime.Now);
Console.WriteLine($"Next annual reminder: {nextReminder:MMM dd, yyyy}");