Cronos .NET Library

repository·main·Indexed 22 days ago

https://github.com/hangfireio/cronos

A .NET library for parsing Cron expressions and calculating their next or previous occurrences. It is designed to handle time zones and Daylight Saving Time (DST) transitions correctly, supporting standard 5-field expressions, optional seconds via CronFormat.IncludeSeconds, and schedule jitter using the H character. Cronos provides logic for Cron expressions but is not a task scheduler.

Tokens
3.6K
Snippets
12
Records
14
Agent score
29%

What's inside Cronos

  1. Use base characters in Cron expressions

    main

    You can construct expressions using the following base characters:

    • *: Every value in the field.
    • -: Specifies a range (e.g., 1-5). Reversed ranges like 22-1 are supported (equivalent to 22,23,0,1,2).
    • /: Defines steps when combined with *, H, numbers, or ranges. For example, */5 means every 5 units. Note that */24 in a minute field means 0,24,48, not every 24 minutes.
    • ,: Acts as an OR operator to concatenate values or ranges (e.g., 3,5-11/3,12).
    • H: Schedule Jitter. Used to choose a single value determined by the implementation to distribute load.
    • Names: In month and day-of-week fields, use three-letter abbreviations (e.g., JAN-DEC, MON-SUN). Full names are not supported.
    * * * * *
    0  0 1 * *
    */5 * * * *
    30,45-15/2 1 * * *
    0 0 * * MON-FRI
  2. Use schedule jitter with the H character

    main

    Cronos supports "schedule jitter" to distribute cron jobs randomly, spreading out system load. You can enable this by using the special character H in your cron expression and providing a seed (an integer) to the parsing method.

    Important:

    • If you use H in an expression but fail to provide a seed, Cronos will throw an exception.
    • When H is used for the day of the month, the range is automatically limited to the first 28 days to prevent impossible dates.
    • For job-scheduling scenarios, it is recommended to use a unique identifier (like a jobId.GetHashCode()) as the seed to ensure consistent jitter for that specific job.
    // Example of using a seed to enable jitter
    var seed = jobId.GetHashCode();
    
    // Using H in a string expression
    var hourlyExpressionWithJitter = CronExpression.Parse("H H * * * *", CronFormat.IncludeSeconds, seed);
    
    // Using a macro with a seed
    var hourlyMacroWithJitter = CronExpression.Parse("@hourly", seed);
    
    // Using a named property with a seed
    var hourlyNamedWithJitter = CronExpression.HourlyWithJitter(seed);
  3. Understand Daylight Saving Time (DST) behavior

    main

    Cronos handles DST transitions intuitively, following Vixie Cron behavior.

    Spring Transition (Clock moves forward)

    If a scheduled time becomes invalid (e.g., 02:30 AM during a jump from 01:59 to 03:00), Cronos adjusts the occurrence to the next valid time.

    Autumn Transition (Clock moves backward)

    When the clock repeats an hour, behavior depends on the expression type:

    1. Interval-based expressions: If the second, minute, or hour field contains *, ranges, or steps (e.g., */30 * * * *), the expression is treated as periodic. It will run both before and after the clock shift to maintain the interval.
    2. Non-interval expressions: If the second, minute, or hour fields contain specific values without *, ranges, or steps (e.g., 0 30 1 * * *), the expression is treated as a fixed daily occurrence. It will run once, typically just before the clock shift, to avoid duplicate runs during the repeated hour.
  4. Understand the Cron expression format

    main

    Cronos uses a mask to define fixed times, dates, and intervals. The expression consists of six fields (the first being an optional second field), followed by minute, hour, day-of-month, month, and day-of-week. An occurrence satisfies the expression if all fields contain a matching value.

    Field Order: second (optional) minute hour day-of-month month day-of-week

    Field Constraints:

    • Second: 0-59
    • Minute: 0-59
    • Hour: 0-23
    • Day of Month: 1-31
    • Month: 1-12 or JAN-DEC
    • Day of Week: 0-6 or SUN-SAT (Note: both 0 and 7 represent Sunday)
  5. Specify both Day of Month and Day of Week

    main

    Unlike Quartz, Cronos allows you to set both day-of-month and day-of-week simultaneously. This enables constructs like "Friday the 13th".

    In Cronos, setting both fields acts as an AND condition (the occurrence must satisfy both), whereas in Unix crontab, it acts as an OR condition.

    0 0 13 * 5  // At 00:00, on Friday the 13th
  6. Working with time zones in Cronos

    main

    Cronos handles time zone conversions and Daylight Saving Time (DST) transitions intuitively. You can specify a TimeZoneInfo when calculating occurrences.

    Using DateTime (UTC Result)

    If you pass a DateTime with DateTimeKind.Utc, the resulting occurrence will be returned in UTC.

    CronExpression expression = CronExpression.Parse("* * * * *");
    TimeZoneInfo easternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
    
    DateTime? next = expression.GetNextOccurrence(DateTime.UtcNow, easternTimeZone);

    Using DateTimeOffset (Offset-Aware Result)

    If you use DateTimeOffset, the resulting object will contain the correct offset, which is critical during DST transitions.

    CronExpression expression = CronExpression.Parse("* * * * *");
    TimeZoneInfo easternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
    
    DateTimeOffset? next = expression.GetNextOccurrence(DateTimeOffset.UtcNow, easternTimeZone);
    CronExpression expression = CronExpression.Parse("* * * * *");
    TimeZoneInfo easternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
    
    DateTime?       next = expression.GetNextOccurrence(DateTime.UtcNow, easternTimeZone);
    DateTimeOffset? next = expression.GetNextOccurrence(DateTimeOffset.UtcNow, easternTimeZone);
    DateTime?       previous = expression.GetPreviousOccurrence(DateTime.UtcNow, easternTimeZone);
    DateTimeOffset? previous = expression.GetPreviousOccurrence(DateTimeOffset.UtcNow, easternTimeZone);
  7. How to use Cronos to calculate occurrences

    main

    Cronos is a .NET library for parsing Cron expressions and calculating next or previous occurrences. It is not a task scheduler; it only handles the logic of Cron expressions.

    Core Usage Pattern

    1. Parse a Cron expression using CronExpression.Parse().
    2. Calculate the next or previous occurrence using GetNextOccurrence() or GetPreviousOccurrence().

    Important: DateTime Constraints

    To avoid ambiguity during Daylight Saving Time (DST) transitions, you cannot use local DateTime objects (like DateTime.Now). You must use:

    • DateTime with DateTimeKind.Utc (e.g., DateTime.UtcNow)
    • DateTimeOffset classes

    If you attempt to use a local DateTime, an exception will be thrown.

    Basic Example

    using Cronos;
    
    CronExpression expression = CronExpression.Parse("* * * * *");
    
    // Returns the next occurrence in UTC after the given time, or null if unreachable
    DateTime? nextUtc = expression.GetNextOccurrence(DateTime.UtcNow);
    
    // Returns the most recent occurrence in UTC before the given time, or null if none exist
    DateTime? previousUtc = expression.GetPreviousOccurrence(DateTime.UtcNow);

    If an invalid Cron expression is provided, a CronFormatException is thrown.

    using Cronos;
    
    CronExpression expression = CronExpression.Parse("* * * * *");
    
    DateTime? nextUtc = expression.GetNextOccurrence(DateTime.UtcNow);
    DateTime? previousUtc = expression.GetPreviousOccurrence(DateTime.UtcNow);
  8. Working with local time in Cronos

    main

    To perform calculations using local time, you must use the DateTimeOffset class to avoid DST ambiguity. You can then access the local time via the .DateTime property.

    CronExpression expression = CronExpression.Parse("* * * * *");
    
    // Calculate using the local time zone
    DateTimeOffset? next = expression.GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local);
    
    // Access the resulting local time
    var nextLocalTime = next?.DateTime;
    CronExpression expression = CronExpression.Parse("* * * * *");
    DateTimeOffset? next = expression.GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local);
    
    var nextLocalTime = next?.DateTime;
  9. Adding seconds to a Cron expression

    main

    By default, Cronos parses standard 5-field Cron expressions. To support expressions that include seconds, use the Parse overload with CronFormat.IncludeSeconds.

    // Example: Every 30 seconds
    CronExpression expression = CronExpression.Parse("*/30 * * * * *", CronFormat.IncludeSeconds);
    DateTime? next = expression.GetNextOccurrence(DateTime.UtcNow);
    CronExpression expression = CronExpression.Parse("*/30 * * * * *", CronFormat.IncludeSeconds);
    DateTime? next = expression.GetNextOccurrence(DateTime.UtcNow);
  10. Getting occurrences within a range

    main

    You can retrieve multiple occurrences within a specific date/time range using GetOccurrences or GetOccurrencesDescending.

    GetOccurrences (Forward)

    Returns occurrences between from and to.

    • By default, from is inclusive and to is exclusive.
    • You can configure this via fromInclusive and toInclusive parameters.
    CronExpression expression = CronExpression.Parse("* * * * *");
    IEnumerable<DateTime> occurrences = expression.GetOccurrences(
        DateTime.UtcNow,
        DateTime.UtcNow.AddYears(1),
        fromInclusive: true,
        toInclusive: false);

    GetOccurrencesDescending (Reverse)

    Returns occurrences in reverse order. The from argument acts as the upper bound and the to argument acts as the lower bound.

    IEnumerable<DateTime> previousOccurrences = expression.GetOccurrencesDescending(
        DateTime.UtcNow,
        DateTime.UtcNow.AddDays(-7),
        fromInclusive: true,
        toInclusive: false);
    CronExpression expression = CronExpression.Parse("* * * * *");
    IEnumerable<DateTime> occurrences = expression.GetOccurrences(
        DateTime.UtcNow,
        DateTime.UtcNow.AddYears(1),
        fromInclusive: true,
        toInclusive: false);
    
    IEnumerable<DateTime> previousOccurrences = expression.GetOccurrencesDescending(
        DateTime.UtcNow,
        DateTime.UtcNow.AddDays(-7),
        fromInclusive: true,
        toInclusive: false);
  11. Use Cron macros for shortcuts

    main

    Macros are strings starting with @ that act as shortcuts for common schedules.

    @every_second | * * * * * *
    @every_minute | * * * * *
    @hourly       | 0 * * * *
    @daily        | 0 0 * * *
    @midnight     | 0 0 * * *
    @weekly       | 0 0 * * 0
    @monthly      | 0 0 1 * *
    @yearly       | 0 0 1 1 *
    @annually     | 0 0 1 1 *