Skip to main content

Vali-Schedule

IValiSchedule is a fluent builder for recurring schedules. It supports daily, weekly, monthly, and yearly recurrence patterns with start date, end conditions, and custom predicates.

Installation

dotnet add package Vali-Schedule

Registration

builder.Services.AddValiSchedule();
// or via the meta-package:
builder.Services.AddValiTempo();
Thread safety

IValiSchedule is not thread-safe and should not be registered as a singleton. Register it as scoped or transient, or create new instances as needed. The builder pattern is stateful between calls.

Enums

RecurrenceType

Defines how often the schedule repeats:

ValueDescription
DailyRepeats every N days
WeeklyRepeats every N weeks on specified days
MonthlyRepeats every N months on a specified day
YearlyRepeats every N years on a specified month and day

RecurrenceEnd

Defines when the schedule stops:

ValueDescription
NeverThe schedule repeats indefinitely
AfterOccurrencesStops after a fixed number of occurrences
OnDateStops on or after a specific end date

IValiSchedule — Fluent Builder API

Every

Set the interval and recurrence type:

schedule.Every(1, RecurrenceType.Daily)    // every day
schedule.Every(2, RecurrenceType.Weekly) // every 2 weeks
schedule.Every(1, RecurrenceType.Monthly) // every month
schedule.Every(1, RecurrenceType.Yearly) // every year

Signature: IValiSchedule Every(int interval, RecurrenceType type)

On

For weekly schedules: specify which days of the week to include:

schedule.Every(1, RecurrenceType.Weekly)
.On(DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday)

Signature: IValiSchedule On(params DayOfWeek[] days)

At

Set the time of day for occurrences:

schedule.Every(1, RecurrenceType.Daily)
.At(9, 0) // 9:00 AM

Signature: IValiSchedule At(int hour, int minute = 0, int second = 0)

StartingFrom

Set the start date (default is DateTime.Today):

schedule.Every(1, RecurrenceType.Weekly)
.StartingFrom(new DateTime(2025, 4, 1))

Signature: IValiSchedule StartingFrom(DateTime startDate)

EndsAfter

Stop the schedule after a fixed number of occurrences:

schedule.Every(1, RecurrenceType.Daily)
.EndsAfter(10) // 10 occurrences then stop

Signature: IValiSchedule EndsAfter(int occurrences)

EndsOn

Stop the schedule on or after a specific date:

schedule.Every(1, RecurrenceType.Monthly)
.EndsOn(new DateTime(2026, 12, 31))

Signature: IValiSchedule EndsOn(DateTime endDate)

OnDayOfMonth

For monthly schedules: specify which day of the month:

schedule.Every(1, RecurrenceType.Monthly)
.OnDayOfMonth(15) // 15th of every month

Signature: IValiSchedule OnDayOfMonth(int day)

WithCustomPredicate

Add a custom filter — only occurrences where the predicate returns true are included:

schedule.Every(1, RecurrenceType.Daily)
.WithCustomPredicate(date => date.DayOfWeek != DayOfWeek.Friday)
// Skips Fridays

Signature: IValiSchedule WithCustomPredicate(Func<DateTime, bool> predicate)

Build

Finalize the builder and return a read-only Schedule object:

var sched = schedule
.Every(1, RecurrenceType.Weekly)
.On(DayOfWeek.Monday)
.At(9, 0)
.StartingFrom(new DateTime(2025, 1, 6))
.Build();

Signature: Schedule Build()

Query Methods

Once you have a Schedule, use these methods to query occurrences:

NextOccurrence

Return the next occurrence after a given date:

DateTime? next = sched.NextOccurrence(DateTime.Now);
// → null if schedule has ended

Signature: DateTime? NextOccurrence(DateTime after)

PreviousOccurrence

Return the most recent occurrence before a given date:

DateTime? prev = sched.PreviousOccurrence(DateTime.Now);

Signature: DateTime? PreviousOccurrence(DateTime before)

OccursOn

Return true if the schedule has an occurrence on the given date (ignoring time):

bool occurs = sched.OccursOn(new DateTime(2025, 4, 7));

Signature: bool OccursOn(DateTime date)

Occurrences

Enumerate the next N occurrences from a given date:

IEnumerable<DateTime> next5 = sched.Occurrences(5, DateTime.Now);

foreach (var occ in next5)
Console.WriteLine(occ.ToString("g"));

Signature: IEnumerable<DateTime> Occurrences(int count, DateTime from)

OccurrencesInRange

Enumerate all occurrences within a date range:

var range = new DateRange(new DateTime(2025, 4, 1), new DateTime(2025, 6, 30));
IEnumerable<DateTime> q2Meetings = sched.OccurrencesInRange(range);

Signature: IEnumerable<DateTime> OccurrencesInRange(DateRange range)

Complete Examples

Weekly standup

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}");
}
}

Monthly payroll run

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))
// Skip months where the 25th falls on weekend
.WithCustomPredicate(d =>
d.DayOfWeek != DayOfWeek.Saturday &&
d.DayOfWeek != DayOfWeek.Sunday)
.Build();
}
}

Yearly reminder with end condition

var reminder = schedule
.Every(1, RecurrenceType.Yearly)
.At(10, 0)
.StartingFrom(new DateTime(2025, 3, 23))
.EndsAfter(5) // 5 years only
.Build();

DateTime? nextReminder = reminder.NextOccurrence(DateTime.Now);
Console.WriteLine($"Next annual reminder: {nextReminder:MMM dd, yyyy}");