ScheduleKit 1.0.0
dotnet add package ScheduleKit --version 1.0.0
NuGet\Install-Package ScheduleKit -Version 1.0.0
<PackageReference Include="ScheduleKit" Version="1.0.0" />
<PackageVersion Include="ScheduleKit" Version="1.0.0" />
<PackageReference Include="ScheduleKit" />
paket add ScheduleKit --version 1.0.0
#r "nuget: ScheduleKit, 1.0.0"
#:package ScheduleKit@1.0.0
#addin nuget:?package=ScheduleKit&version=1.0.0
#tool nuget:?package=ScheduleKit&version=1.0.0
ScheduleKit
Business hours, holiday calendars, and time calculations for SLA-aware scheduling in .NET.
Inspired by spatie/opening-hours and spatie/holidays.
Table of Contents
- Overview
- Packages
- Installation
- Quick Start
- Building a Schedule
- Calculations
- Dependency Injection
- ASP.NET Core Integration
- Algorithm Details
- Multi-targeting
- License
Overview
ScheduleKit is a pure .NET calculation library with no storage dependencies. It gives you:
- A fluent builder (
BusinessSchedule) to define weekly opening hours per day of the week - A timezone-aware calculator (
IBusinessScheduleCalculator) that adds business time, computes elapsed time, checks if a moment is open, and finds the next opening - A holiday system with built-in US federal holidays and full extensibility for any country or custom date
- DI integration (
IScheduleCalculator+IScheduleProvider) so you can load schedules from a database, config, or any source
All results are returned as UTC DateTimeOffset values. Schedules never touch a database — consumers own persistence.
Packages
| Package | Description |
|---|---|
ScheduleKit |
Core library — schedule builder, calculator, holidays, DI extensions |
ScheduleKit.AspNetCore |
ASP.NET Core helpers — scoped provider registration |
Installation
dotnet add package ScheduleKit
# or for ASP.NET Core projects:
dotnet add package ScheduleKit.AspNetCore
Quick Start
using ScheduleKit.Builder;
using ScheduleKit.Calculation;
using ScheduleKit.Holidays;
// 1. Define a schedule
var schedule = new BusinessSchedule()
.Monday("09:00", "17:00")
.Tuesday("09:00", "17:00")
.Wednesday("09:00", "17:00")
.Thursday("09:00", "17:00")
.Friday("09:00", "17:00")
.Saturday() // closed
.Sunday() // closed
.WithTimezone("America/New_York")
.WithHolidays(HolidayCalendar.ForCountry("US", 2026));
// 2. Instantiate the calculator
var calculator = new BusinessScheduleCalculator();
// 3. Add 4 business hours to a ticket creation time
var ticketCreated = new DateTimeOffset(2026, 11, 25, 14, 0, 0, TimeSpan.Zero); // Wed 14:00 UTC
var deadline = calculator.AddBusinessTime(ticketCreated, TimeSpan.FromHours(4), schedule);
// Wed has 1h left (14:00–17:00 ET = 19:00–22:00 UTC? — no, schedule timezone is ET)
// Result: Thu Nov 26 is Thanksgiving (skipped), so spills to Fri Nov 27
// 4. Check if a moment is within business hours
bool isOpen = calculator.IsWithinBusinessHours(DateTimeOffset.UtcNow, schedule);
// 5. Calculate elapsed business time between two points
var elapsed = calculator.ElapsedBusinessTime(ticketCreated, deadline, schedule);
Building a Schedule
Weekday Hours
Use the fluent weekday methods. Each method accepts "HH:mm" 24-hour open/close strings.
Calling the parameterless overload marks the day as closed.
var schedule = new BusinessSchedule()
.Monday("09:00", "17:00")
.Tuesday("09:00", "17:00")
.Wednesday("09:00", "17:00")
.Thursday("09:00", "17:00")
.Friday("09:00", "14:00") // half day
.Saturday() // closed
.Sunday(); // closed
All methods return a new BusinessSchedule — the original is never mutated, so you can safely derive multiple variants from a base schedule:
var base = new BusinessSchedule()
.Monday("09:00", "17:00")
.Friday("09:00", "17:00");
var withLunchBreak = base.Wednesday("09:00", "12:00"); // only Wednesday differs
var withFullWednes = base.Wednesday("09:00", "17:00");
Timezone Support
// IANA timezone IDs (cross-platform)
var schedule = new BusinessSchedule()
.Monday("09:00", "17:00")
.WithTimezone("America/New_York");
// Windows timezone IDs (also accepted)
var schedule2 = new BusinessSchedule()
.Monday("09:00", "17:00")
.WithTimezone("Eastern Standard Time");
All calculations convert the DateTimeOffset inputs to the schedule's timezone before applying business-hours logic. Results are always returned in UTC.
Holidays
Holidays are treated as fully closed days regardless of the day-of-week configuration.
Built-in US Holidays
using ScheduleKit.Holidays;
// Get all 11 US federal holidays for 2026
IReadOnlyList<Holiday> us2026 = HolidayCalendar.ForCountry("US", 2026);
var schedule = new BusinessSchedule()
.Monday("09:00", "17:00")
// ... other days
.WithHolidays(us2026);
Supported countries: "US" (case-insensitive).
US holidays included (with standard Saturday→Friday / Sunday→Monday observation):
- New Year's Day (Jan 1)
- Martin Luther King Jr. Day (3rd Monday in January)
- Presidents' Day (3rd Monday in February)
- Memorial Day (last Monday in May)
- Juneteenth National Independence Day (Jun 19)
- Independence Day (Jul 4)
- Labor Day (1st Monday in September)
- Columbus Day (2nd Monday in October)
- Veterans Day (Nov 11)
- Thanksgiving Day (4th Thursday in November)
- Christmas Day (Dec 25)
Custom Holidays
// Add a single date
var schedule = new BusinessSchedule()
.Monday("09:00", "17:00")
.WithCustomHoliday(new DateOnly(2026, 12, 26), "Boxing Day");
// Or supply a DateTime
var schedule2 = schedule.WithCustomHoliday(new DateTime(2026, 4, 10), "Good Friday");
// Or compose from any IEnumerable<Holiday>
var companyHolidays = new[]
{
new Holiday(new DateOnly(2026, 7, 3), "Company Summer Day"),
new Holiday(new DateOnly(2026, 12, 24), "Christmas Eve"),
};
var schedule3 = new BusinessSchedule()
.Monday("09:00", "17:00")
.WithHolidays(companyHolidays);
Calculations
All calculations require a BusinessSchedule and operate in the schedule's configured timezone.
AddBusinessTime
Add a TimeSpan of business time to a starting point, skipping weekends and holidays.
var calculator = new BusinessScheduleCalculator();
// Friday 15:00 UTC + 4 business hours
// (2 hours remain Friday, then skip weekend, continue Monday morning)
var from = new DateTimeOffset(2026, 3, 27, 15, 0, 0, TimeSpan.Zero);
var result = calculator.AddBusinessTime(from, TimeSpan.FromHours(4), schedule);
// => Monday 2026-03-30 11:00 UTC
Algorithm:
- Convert
fromto the schedule's timezone. - If outside business hours, advance to the next opening time.
- While
remaining > 0:- If remaining fits within today's remaining business hours → advance by that amount → done.
- Otherwise → subtract today's remaining hours, advance to the next business day's opening.
- Return result in UTC.
ElapsedBusinessTime
Calculate how much business time has passed between two points.
var from = new DateTimeOffset(2026, 3, 27, 14, 0, 0, TimeSpan.Zero); // Friday 14:00
var to = new DateTimeOffset(2026, 3, 30, 12, 0, 0, TimeSpan.Zero); // Monday 12:00
var elapsed = calculator.ElapsedBusinessTime(from, to, schedule);
// => 6 hours (Fri 14–17 = 3h, Mon 09–12 = 3h, weekend excluded)
Algorithm:
Walk day-by-day from from to to, summing only the portions of each day that overlap with the day's business hours window. Days that are closed (weekends, holidays) contribute zero.
IsWithinBusinessHours
Check whether a specific moment falls within business hours.
bool isOpen = calculator.IsWithinBusinessHours(
DateTimeOffset.UtcNow, schedule);
The close time is exclusive — a moment exactly at the closing time returns false.
NextOpeningTime
Find the next moment the business will be open. Returns the input moment itself if it is already within business hours.
// Saturday noon → returns Monday 09:00
var next = calculator.NextOpeningTime(
new DateTimeOffset(2026, 3, 28, 12, 0, 0, TimeSpan.Zero), schedule);
Dependency Injection
Default Schedule
Register ScheduleKit with a hard-coded schedule:
services.AddScheduleKit(options =>
{
options.DefaultSchedule = new BusinessSchedule()
.Monday("09:00", "17:00")
.Tuesday("09:00", "17:00")
.Wednesday("09:00", "17:00")
.Thursday("09:00", "17:00")
.Friday("09:00", "17:00")
.Saturday()
.Sunday()
.WithTimezone("America/New_York")
.WithHolidays(HolidayCalendar.ForCountry("US", DateTimeOffset.UtcNow.Year));
});
Custom Schedule Provider
Implement IScheduleProvider to load schedules from any source (database, per-tenant config, etc.):
public class TenantScheduleProvider(AppDbContext db) : IScheduleProvider
{
public async Task<BusinessSchedule> GetScheduleAsync(
object? scopeId = null,
CancellationToken ct = default)
{
var tenantId = scopeId as int? ?? throw new InvalidOperationException("scopeId must be a tenant ID.");
var row = await db.TenantSchedules.FindAsync([tenantId], ct)
?? throw new InvalidOperationException($"No schedule found for tenant {tenantId}.");
return new BusinessSchedule()
.Monday(row.MondayOpen, row.MondayClose)
.WithTimezone(row.TimezoneId);
}
}
Register it:
services.AddScheduleKit(options =>
{
options.ResolveScheduleFrom<TenantScheduleProvider>();
});
IScheduleCalculator
Inject IScheduleCalculator to resolve the schedule automatically from the provider:
public class SlaService(IScheduleCalculator calculator)
{
public async Task<DateTimeOffset> GetDeadlineAsync(
DateTimeOffset ticketCreated,
TimeSpan sla,
int tenantId,
CancellationToken ct = default)
{
return await calculator.AddBusinessTimeAsync(ticketCreated, sla, tenantId, ct);
}
}
IScheduleCalculator exposes:
| Method | Description |
|---|---|
AddBusinessTimeAsync |
Add business duration to a start point |
ElapsedBusinessTimeAsync |
Compute elapsed business time |
IsWithinBusinessHoursAsync |
Check if a moment is open |
NextOpeningTimeAsync |
Find next opening moment |
ASP.NET Core Integration
Install the integration package:
dotnet add package ScheduleKit.AspNetCore
Register a scoped provider that can take a dependency on IHttpContextAccessor or other scoped services:
using ScheduleKit.AspNetCore.DependencyInjection;
services.AddScheduleKitWithProvider<MyHttpContextAwareProvider>(options =>
{
options.DefaultSchedule = new BusinessSchedule()
.Monday("09:00", "17:00");
});
Algorithm Details
AddBusinessTime
Given from and duration:
- Convert
fromto the schedule timezone. - If the current moment is outside business hours (before open, after close, closed day, holiday) → advance to the next opening time.
- Loop:
- Compute time remaining until close on the current day.
- If
remaining <= timeLeftToday→ move forward byremaining→ done. - Otherwise → subtract
timeLeftTodayfromremaining→ advance to the opening of the next business day.
- Return result converted to UTC.
The loop is guarded with a 3650-iteration limit (10 years of daily iterations). If no open day is found within that limit, an InvalidOperationException is thrown — this protects against schedules with no open days.
ElapsedBusinessTime
Given from and to:
- Walk day by day from
fromtoto. - For each calendar day:
- If it is a non-business day (weekend or holiday) → skip.
- Otherwise, clamp the time range to
[openTime, closeTime]for that day and add the overlap to the running total.
- Return total elapsed business time as a
TimeSpan.
Multi-targeting
| Package | Target Frameworks |
|---|---|
ScheduleKit |
net8.0, net9.0, net10.0 |
ScheduleKit.AspNetCore |
net8.0, net10.0 |
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.3)
- Microsoft.Extensions.Options (>= 10.0.3)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.3)
- Microsoft.Extensions.Options (>= 9.0.3)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.3)
- Microsoft.Extensions.Options (>= 9.0.3)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on ScheduleKit:
| Package | Downloads |
|---|---|
|
ScheduleKit.AspNetCore
ASP.NET Core integration for ScheduleKit — dependency injection extensions and schedule provider registration. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 168 | 3/26/2026 |