| name | ncrontab |
| description | Guidance for NCrontab cron expression parser and scheduler for .NET. USE FOR: parsing cron expressions, calculating next/previous occurrences, validating cron syntax, scheduling background tasks with cron patterns, generating occurrence lists for display. DO NOT USE FOR: full job scheduling frameworks (use Quartz.NET or Hangfire), distributed task scheduling, Windows Task Scheduler integration, real-time event processing.
|
| license | MIT |
| metadata | {"displayName":"NCrontab","author":"Tyler-R-Kendrick","version":"1.0.0"} |
| compatibility | ["claude","copilot","cursor"] |
| references | [{"title":"NCrontab GitHub Repository","url":"https://github.com/atifaziz/NCrontab"},{"title":"NCrontab NuGet Package","url":"https://www.nuget.org/packages/NCrontab"}] |
NCrontab
Overview
NCrontab is a lightweight library for parsing cron expressions and calculating occurrence times in .NET. It implements standard five-field cron syntax (minute, hour, day-of-month, month, day-of-week) and an extended six-field format that adds seconds. NCrontab does not execute tasks itself -- it purely handles cron parsing and schedule calculation, making it ideal for use in custom schedulers, worker services, or any code that needs to compute "when does this cron expression next fire?"
NCrontab is a dependency-free library that works with all .NET versions including .NET Core, .NET 5+, and .NET Framework.
Install via NuGet:
dotnet add package NCrontab
Parsing Cron Expressions
Parse a cron expression string into a CrontabSchedule object for occurrence calculation.
using NCrontab;
var daily = CrontabSchedule.Parse("0 0 * * *");
var hourly = CrontabSchedule.Parse("0 * * * *");
var weekdays = CrontabSchedule.Parse("30 9 * * 1-5");
var quarterly = CrontabSchedule.Parse("0 0 1 1,4,7,10 *");
var options = new CrontabSchedule.ParseOptions { IncludingSeconds = true };
var everyTenSeconds = CrontabSchedule.Parse("*/10 * * * * *", options);
var atMidnight = CrontabSchedule.Parse("0 0 0 * * *", options);
Console.WriteLine($"Next daily: {daily.GetNextOccurrence(DateTime.Now)}");
Console.WriteLine($"Next hourly: {hourly.GetNextOccurrence(DateTime.Now)}");
Calculating Occurrences
Get the next occurrence, or enumerate multiple future occurrences within a time range.
using System;
using System.Linq;
using NCrontab;
var schedule = CrontabSchedule.Parse("0 9 * * 1");
var now = DateTime.Now;
var nextOccurrence = schedule.GetNextOccurrence(now);
Console.WriteLine($"Next Monday 9 AM: {nextOccurrence:yyyy-MM-dd HH:mm}");
var startDate = new DateTime(2025, 1, 1);
var endDate = new DateTime(2025, 3, 31);
var occurrences = schedule.GetNextOccurrences(startDate, endDate).ToList();
Console.WriteLine($"Mondays at 9 AM in Q1 2025: {occurrences.Count}");
foreach (var occurrence in occurrences.Take(5))
{
Console.WriteLine($" {occurrence:yyyy-MM-dd dddd HH:mm}");
}
var timeUntilNext = nextOccurrence - now;
Console.WriteLine($"Time until next: {timeUntilNext.TotalHours:F1} hours");
Validating Cron Expressions
Validate user-provided cron expressions before storing them in configuration.
using NCrontab;
public static class CronValidator
{
public static (bool IsValid, string? Error) Validate(
string expression, bool includeSeconds = false)
{
try
{
var options = new CrontabSchedule.ParseOptions
{
IncludingSeconds = includeSeconds
};
var schedule = CrontabSchedule.Parse(expression, options);
var next = schedule.GetNextOccurrence(DateTime.UtcNow);
if (next == default)
{
return (false, "Expression produces no future occurrences");
}
return (true, null);
}
catch (CrontabException ex)
{
return (false, ex.Message);
}
}
public static string Describe(string expression)
{
var parts = expression.Split(' ');
if (parts.Length < 5) return "Invalid expression";
return parts
{
[, , , , ] => ,
[, , , , ] => ,
[, , , , ] => ,
_ =>
};
}
}
(valid, error) = CronValidator.Validate();
Console.WriteLine();
(valid2, error2) = CronValidator.Validate();
Console.WriteLine();
(valid3, error3) = CronValidator.Validate();
Console.WriteLine();
Integrating with BackgroundService
Use NCrontab in a BackgroundService to run tasks on a cron schedule.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NCrontab;
public class CronScheduledWorker : BackgroundService
{
private readonly ILogger<CronScheduledWorker> _logger;
private readonly CrontabSchedule _schedule;
public CronScheduledWorker(ILogger<CronScheduledWorker> logger)
{
_logger = logger;
_schedule = CrontabSchedule.Parse("30 2 * * *");
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var now = DateTime.UtcNow;
var nextRun = _schedule.GetNextOccurrence(now);
var delay = nextRun - now;
_logger.LogInformation(
"Next scheduled run at {NextRun} (in {Delay})",
nextRun, delay);
await Task.Delay(delay, stoppingToken);
if (!stoppingToken.IsCancellationRequested)
{
try
{
_logger.LogInformation("Executing scheduled task");
await ExecuteScheduledTaskAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Scheduled task failed");
}
}
}
}
{
Task.CompletedTask;
}
}
Configurable Multi-Schedule Service
Support multiple cron schedules loaded from configuration.
using System;
using System.Collections.Generic;
using System.Linq;
using NCrontab;
public record ScheduledJob(string Name, string CronExpression, Func<CancellationToken, Task> Action);
public class CronScheduleRegistry
{
private readonly List<(ScheduledJob Job, CrontabSchedule Schedule)> _jobs = new();
public void Register(ScheduledJob job)
{
var schedule = CrontabSchedule.Parse(job.CronExpression);
_jobs.Add((job, schedule));
}
public IReadOnlyList<(string Name, DateTime NextRun)> GetUpcomingRuns(DateTime from, int count)
{
return _jobs
.Select(j => (j.Job.Name, NextRun: j.Schedule.GetNextOccurrence(from)))
.OrderBy(j => j.NextRun)
.Take(count)
.ToList();
}
public (ScheduledJob Job, DateTime NextRun)? GetNextJob(DateTime from)
{
return _jobs
.Select(j => (j.Job, NextRun: j.Schedule.GetNextOccurrence(from)))
.OrderBy(j => j.NextRun)
.Select(j => ((ScheduledJob, DateTime)?)j)
.FirstOrDefault();
}
}
var registry = new CronScheduleRegistry();
registry.Register(new ScheduledJob("Cleanup", "0 3 * * *", _ => Task.CompletedTask));
registry.Register(new ScheduledJob("Report", , _ => Task.CompletedTask));
registry.Register( ScheduledJob(, , _ => Task.CompletedTask));
upcoming = registry.GetUpcomingRuns(DateTime.UtcNow, );
( (name, nextRun) upcoming)
{
Console.WriteLine();
}
Common Cron Patterns
| Pattern | Five-Field Expression | Description |
|---|
| Every minute | * * * * * | Runs every minute |
| Every 5 minutes | */5 * * * * | Runs every 5 minutes |
| Every hour | 0 * * * * | Top of every hour |
| Daily at midnight | 0 0 * * * | Once per day |
| Weekdays at 9 AM | 0 9 * * 1-5 | Mon-Fri at 9:00 |
| First of month | 0 0 1 * * | Midnight, 1st of each month |
| Every Sunday | 0 0 * * 0 | Midnight every Sunday |
| Every 30 minutes | */30 * * * * | Twice per hour |
| Twice daily | 0 8,17 * * * | 8 AM and 5 PM |
| Quarterly | 0 0 1 1,4,7,10 * | First day of each quarter |
Best Practices
- Use UTC times consistently with
DateTime.UtcNow for cron calculations to avoid daylight saving time issues that cause skipped or doubled executions.
- Parse cron expressions once and reuse the
CrontabSchedule instance since parsing involves string splitting and validation that should not repeat per tick.
- Validate cron expressions at application startup or configuration save time rather than at execution time to fail fast on invalid patterns.
- Use the six-field format with
IncludingSeconds = true only when sub-minute precision is genuinely needed -- five-field expressions are more portable and widely understood.
- Document cron patterns in configuration with comments explaining the schedule in plain English, since cron syntax is not self-documenting.
- Account for task execution time when calculating the next occurrence -- if a task takes 5 minutes and runs every 5 minutes, use the task's end time as the base for
GetNextOccurrence.
- Handle the gap between calculated delay and actual wake time by re-checking the current time after
Task.Delay returns, since the OS may wake the task slightly early or late.
- Use
GetNextOccurrences to display upcoming schedules in admin UIs so operators can verify that a cron expression produces the expected pattern.
- Combine NCrontab with
BackgroundService for simple cron-scheduled tasks, but prefer Quartz.NET or Hangfire when you need persistence, retries, or distributed coordination.
- Test cron schedules across time boundaries including month-end, year-end, leap years, and DST transitions to verify occurrence calculation correctness.