Human-readable cron expression converter with full Unix cron specification compliance and bidirectional support. Parse schedules like "every 30 minutes", "every day at 2pm", or "every monday at 9am" into Unix cron expressions and convert back to natural language. Supports complete cron syntax: lists (0,15,30,45), ranges (9-17), steps (*/15, 0-30/5), named values (MON-FRI, JAN-DEC), and mixed syntax. Features smart compaction, zero-allocation Span<T> parsing, NodaTime timezone handling with DST support, and comprehensive month selection.
$ dotnet add package HumanCronHuman-readable cron expression converter with bidirectional support and timezone awareness for .NET. Parse schedules like "every 30 minutes", "every day at 2pm", or "every monday at 9am" into Unix cron expressions or Quartz.NET schedules, and convert cron expressions back to natural language.
# Core library (Unix cron support)
dotnet add package HumanCron
# NCrontab 6-field cron support (optional - adds seconds precision)
dotnet add package HumanCron.NCrontab
# Quartz.NET integration (optional)
dotnet add package HumanCron.Quartz
# Hangfire integration (optional - includes NCrontab support)
dotnet add package HumanCron.Hangfire
using HumanCron.Converters.Unix;
var converter = UnixCronConverter.Create();
// Convert to cron
var result = converter.ToUnixCron("every 30 minutes");
// Returns: "*/30 * * * *"
result = converter.ToUnixCron("every day at 2pm");
// Returns: "0 14 * * *"
result = converter.ToUnixCron("every monday at 9am");
// Returns: "0 9 * * 1"
result = converter.ToUnixCron("every day in january at 9am");
// Returns: "0 9 * 1 *"
result = converter.ToUnixCron("every weekday in january,april,july,october at 9am");
// Returns: "0 9 * 1,4,7,10 1-5"
// Convert back to natural language
var reverseResult = converter.ToNaturalLanguage("0 14 * * *");
// Returns: "every day at 2pm"
using HumanCron;
// In Program.cs or Startup.cs
builder.Services.AddHumanCron();
// In your service
public class MySchedulingService
{
private readonly IHumanCronConverter _converter;
public MySchedulingService(IHumanCronConverter converter)
{
_converter = converter;
}
public void ScheduleJob(string schedule)
{
var cronResult = _converter.ToUnixCron(schedule);
if (cronResult is ParseResult<string>.Success success)
{
// Use success.Value cron expression
}
}
}
using HumanCron.NCrontab;
var converter = NCrontabConverter.Create();
// Convert to 6-field NCrontab (includes seconds)
var result = converter.ToNCrontab("every 30 seconds");
// Returns: "*/30 * * * * *"
result = converter.ToNCrontab("every 15 minutes");
// Returns: "0 */15 * * * *"
result = converter.ToNCrontab("every day at 2pm");
// Returns: "0 0 14 * * *"
result = converter.ToNCrontab("every weekday at 9am");
// Returns: "0 0 9 * * 1-5"
// Convert back to natural language
var reverseResult = converter.ToNaturalLanguage("0 0 14 * * *");
// Returns: "every day at 2pm"
using Hangfire;
using HumanCron.Hangfire.Extensions;
// Option 1: Use natural language strings directly
RecurringJob.AddOrUpdate(
"my-job-id",
"every 30 seconds",
() => DoWork()
);
RecurringJob.AddOrUpdate(
"daily-job",
"every day at 2pm",
() => DailyTask()
);
// Option 2: Use fluent API with ScheduleBuilder
Schedule.Every(30).Seconds()
.AddOrUpdateHangfireJob("job-id", () => DoWork());
Schedule.Every(1).Day()
.At(new TimeOnly(14, 0))
.AddOrUpdateHangfireJob("daily-job", () => DailyTask());
Schedule.Every(1).Day()
.At(new TimeOnly(9, 0))
.OnWeekdays()
.AddOrUpdateHangfireJob("weekday-job", () => WeekdayTask());
// Option 3: Convert to NCrontab expression
var cronExpression = Schedule.Every(15).Minutes().ToNCrontabExpression();
// Returns: "0 */15 * * * *"
using HumanCron.Quartz;
var converter = QuartzScheduleConverterFactory.Create();
// Convert to Quartz IScheduleBuilder
var result = converter.ToQuartzSchedule("every day at 2pm");
if (result is ParseResult<IScheduleBuilder>.Success success)
{
var trigger = TriggerBuilder.Create()
.WithSchedule(success.Value)
.Build();
}
// Multi-week patterns use CalendarIntervalScheduleBuilder
var triggerResult = converter.CreateTriggerBuilder("every 3 weeks on sunday at 12am");
if (triggerResult is ParseResult<TriggerBuilder>.Success triggerSuccess)
{
var trigger = triggerSuccess.Value
.WithIdentity("my-trigger")
.ForJob("my-job")
.Build();
}
All patterns must start with "every":
every 30 seconds, every 45 secondsevery 15 minutes, every 30 minutes, every 45 minutesevery hour, every 6 hours, every 12 hoursevery day, every 7 daysevery week, every 2 weeks, every 3 weeksevery month, every 3 months, every 6 monthsevery year, every 2 yearsat 2pm, at 14:30, at 9am, at 3:30amevery monday, every friday, on tuesdayevery weekday, on weekendsevery monday,wednesday,friday, every mon,wed,fri (specific days)between monday and friday, every tuesday-thursday (compact notation)in january, in decemberbetween january and march, between october and decemberin january,april,july,october (quarterly)on the 15th, on the 1st and 15th, between the 1st and 15thon january 1st, on december 25th, on april 15th (more natural than on the 15th in january)on last day, on day before last, on 3rd to last dayon last friday, on last mondayon 3rd friday, on 2nd tuesday (1st-5th)on weekday nearest the 15th, on last weekdayat hours 9,12,15,18, at minutes 0,15,30,45between hours 9 and 17, between minutes 0 and 30every 5 minutes between 0 and 30 of each hourin year 2025, in year 2024Both full names and abbreviations are accepted on input (output always uses full names):
mon, tue, wed, thu, fri, sat, sunjan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, decevery 30 minutes → */30 * * * *
every hour → 0 * * * *
every day at 2pm → 0 14 * * *
every day at 14:30 → 30 14 * * *
every monday → 0 0 * * 1
every monday at 9am → 0 9 * * 1
every 2 weeks on friday at 5pm → (CalendarInterval schedule)
every weekday at 9am → 0 9 * * 1-5
every monday,wednesday,friday → 0 0 * * 1,3,5
every tuesday-thursday at 9am → 0 9 * * 2-4
every day in january → 0 0 * 1 *
every day in january at 9am → 0 9 * 1 *
every monday in january → 0 0 * 1 1
every day between january and march → 0 0 * 1-3 *
every day in january,april,july,october → 0 0 * 1,4,7,10 *
every weekday in january at 9am → 0 9 * 1 1-5
every month on the 15th → 0 0 15 * *
every month on january 15th → 0 0 15 1 *
every month on january 1st at 1am → 0 1 1 1 *
every month on the 15th in january,april → 0 0 15 1,4 *
every year → 0 0 1 1 *
# Advanced Quartz features (require Quartz converter)
every hour at minutes 0,15,30,45 → 0 0,15,30,45 * * * ?
every day at hours 9,12,15,18 → 0 0 9,12,15,18 * * ?
every 5 minutes between 0 and 30 of each hour → 0 0-30/5 * * * ?
every month on last day → 0 0 L * ?
every month on 3rd friday → 0 0 ? * 6#3
every month on last friday → 0 0 ? * 6L
every month on weekday nearest the 15th → 0 0 15W * ?
every day at 12pm in year 2025 → 0 0 12 * * ? 2025
HumanCron provides full specification compliance for both Unix and Quartz cron formats with bidirectional conversion.
Format: minute hour day-of-month month day-of-week
Supported Syntax:
* (any value)5, 15, MON, JAN0,15,30,45 or MON,WED,FRI or JAN,APR,JUL,OCT1-5, 9-17, JAN-MAR, MON-FRI*/15, 0-30/5, 9-17/20-4,8-12,20 (combines ranges and individual values)JAN-DEC) and day (SUN-SAT) namesSmart Compaction: When building cron expressions, consecutive values are automatically compacted:
[1,2,3,5,7,8,9,10,11,12] → 1-3,5,7-12 (3+ consecutive values become ranges)Examples:
*/30 * * * * → every 30 minutes
0 9-17 * * * → every hour between 9am and 5pm
0 9,12,15,18 * * * → at 9am, 12pm, 3pm, and 6pm
0 9-17/2 * * * → every 2 hours between 9am and 5pm
0 9 * * MON-FRI → every weekday at 9am
0 9 * JAN,APR * MON → every monday in january and april at 9am
0 0 1,15 * * → on the 1st and 15th of each month
0 0 1-15/3 * * → every 3 days between 1st and 15th
Format: second minute hour day-of-month month day-of-week
NCrontab extends Unix cron with a seconds field, enabling second-precision scheduling. Commonly used by Hangfire and Azure Functions.
Supported Syntax (all Unix features plus seconds):
* (any value)5, 30, MON, JAN0,15,30,45 or MON,WED,FRI1-5, 9-17, MON-FRI*/15, 0-30/5, 9-17/20-4,8-12,20Examples:
*/30 * * * * * → every 30 seconds
0 */15 * * * * → every 15 minutes
0 0 14 * * * → every day at 2pm
0 0 9 * * 1-5 → every weekday at 9am
0 0 9 * * MON → every monday at 9am
0 0 9 * 1 * → every day in january at 9am
0 0,15,30,45 * * * * → every 15 minutes
0 0-30/5 9-17 * * * → every 5 minutes in 0-30 range, 9am-5pm
Bidirectional Conversion:
// Natural language → NCrontab
converter.ToNCrontab("every 30 seconds") → "*/30 * * * * *"
converter.ToNCrontab("every 15 minutes") → "0 */15 * * * *"
converter.ToNCrontab("every weekday at 9am") → "0 0 9 * * 1-5"
// NCrontab → natural language
converter.ToNaturalLanguage("*/30 * * * * *") → "every 30 seconds"
converter.ToNaturalLanguage("0 */15 * * * *") → "every 15 minutes"
converter.ToNaturalLanguage("0 0 9 * * 1-5") → "every weekday at 9am"
Format: second minute hour day month day-of-week [year]
Supports all Unix cron features plus Quartz-specific advanced features:
Additional Quartz Features:
? (no specific value): Required when specifying day-of-week or day-of-monthL (last):
L = last day of month6L = last Friday of monthW (weekday):
15W = weekday nearest to the 15thLW = last weekday of month# (nth occurrence): 6#3 = 3rd Friday of month (1-5 valid)L-N (offset from last): L-3 = 3rd to last day of month0 0 12 * * ? 2025Examples:
0 */30 * * * ? → every 30 minutes
0 0 14 * * ? → every day at 2pm
0 0 14 ? * MON → every monday at 2pm
0 0 9 ? * MON-FRI → every weekday at 9am
0 0 9 L * ? → last day of month at 9am
0 0 9 L-3 * ? → 3rd to last day of month at 9am
0 0 9 15W * ? → weekday nearest to 15th at 9am
0 0 9 LW * ? → last weekday of month at 9am
0 0 9 ? * 6#3 → 3rd friday of month at 9am
0 0 9 ? * 6L → last friday of month at 9am
0 0 12 * * ? 2025 → every day at noon in 2025 only
0 0 9-17/2 * * ? → every 2 hours between 9am-5pm
0 0,15,30,45 * * * ? → every 15 minutes
0 0-30/5 9-17 * * ? → every 5 minutes in 0-30 range, 9am-5pm
Both parsers support complete round-trip conversion with smart compaction and verbose natural language output:
// Unix cron → natural language (basic examples)
converter.ToNaturalLanguage("*/30 * * * *") → "every 30 minutes"
converter.ToNaturalLanguage("0 14 * * *") → "every day at 2pm"
converter.ToNaturalLanguage("0 9 * * 1-5") → "every weekday at 9am"
// Lists with smart compaction (3+ consecutive values become ranges)
converter.ToNaturalLanguage("0,15,30,45 * * * *") → "every hour at minutes 0,15,30,45"
converter.ToNaturalLanguage("0 9,12,15,18 * * *") → "every day at hours 9,12,15,18"
converter.ToNaturalLanguage("0 0 1,2,3,4,8,9,10 * *") → "every month on the 1st-4th,8th-10th"
converter.ToNaturalLanguage("0-30/5 * * * *") → "every 5 minutes between 0 and 30 of each hour"
// Ranges
converter.ToNaturalLanguage("0-30 * * * *") → "every hour between minutes 0 and 30"
converter.ToNaturalLanguage("0 9-17 * * *") → "every day between hours 9 and 17"
converter.ToNaturalLanguage("0 0 1-15 * *") → "every month between the 1st and 15th"
// Quartz cron → natural language (advanced features)
quartzConverter.ToNaturalLanguage("0 0 9 L * ?") → "every month on last day at 9am"
quartzConverter.ToNaturalLanguage("0 0 9 L-3 * ?") → "every month on 3rd to last day at 9am"
quartzConverter.ToNaturalLanguage("0 0 9 L-1 * ?") → "every month on day before last at 9am"
quartzConverter.ToNaturalLanguage("0 0 9 15W * ?") → "every month on weekday nearest the 15th at 9am"
quartzConverter.ToNaturalLanguage("0 0 9 LW * ?") → "every month on last weekday at 9am"
quartzConverter.ToNaturalLanguage("0 0 9 ? * 6#3") → "every month on 3rd friday at 9am"
quartzConverter.ToNaturalLanguage("0 0 9 ? * 6L") → "every month on last friday at 9am"
quartzConverter.ToNaturalLanguage("0 0 12 * * ? 2025") → "every day at 12pm in year 2025"
// Natural language → cron → natural language (preserves meaning)
var cron = converter.ToUnixCron("every weekday at 9am"); // "0 9 * * 1-5"
var back = converter.ToNaturalLanguage(cron); // "every weekday at 9am"
// Combined month+day syntax (more natural phrasing)
var cron1 = converter.ToUnixCron("every month on january 1st at 1am"); // "0 1 1 1 *"
var back1 = converter.ToNaturalLanguage(cron1); // "every month on january 1st at 1am"
// Complex patterns with multiple constraints
var cron2 = converter.ToUnixCron("every day at hours 9,12,15,18 in january"); // "0 9,12,15,18 * 1 *"
var back2 = converter.ToNaturalLanguage(cron2); // "every day at hours 9,12,15,18 in january"
Smart Compaction: The natural language formatter automatically compacts consecutive sequences:
[0,1,2,3,4] → "0-4" (5 consecutive values)[0,1,2,8,9,10] → "0-2,8-10" (two sequences of 3+)[0,15,30,45] → "0,15,30,45" (non-consecutive, keep as list)Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.