cron

package module
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2026 License: MIT Imports: 8 Imported by: 0

README

Cron

Don't waste another hour configuring crontabs. Spin up, manage, and debug scheduled tasks in seconds with Crystade - the next-level SaaS for modern cron management.

banner

Cron is a fork of robfig/cron. Compared to the original project:

  • The scheduler is removed
  • The syntax is modified:
    • Only CRON_TZ= is permitted to represent timezone (the original permits CRON_TZ= and TZ=)
    • Only * is permitted to represent "every-step" (the original permits both * and ?)
    • Descriptors are unsupported
    • Seconds are unsupported
  • The default timezone is UTC (the original sets default to local)
  • Customizable year limit for the scheduling algorithm

Installation

go get github.com/crystade/cron

Usage

Basic Usage
package main

import (
    "fmt"
    "log"
    "time"
    
    "github.com/crystade/cron"
)

func main() {
    // Parse a cron expression
    schedule, err := cron.Parse("0 9 * * mon-fri")
    if err != nil {
        log.Fatal(err)
    }
    
    // Get the next execution time
    next := schedule.Next(time.Now())
    fmt.Println("Next run:", next)
}
Custom Parser Configuration
// Create a parser with custom year limit
parser := cron.NewParser(10) // Search up to 10 years ahead
schedule, err := parser.Parse("0 0 29 2 *") // Feb 29 (leap years only)
if err != nil {
    log.Fatal(err)
}

next := schedule.Next(time.Now())
fmt.Println("Next leap year Feb 29:", next)
Minimum Interval Enforcement

The parser supports a min-interval feature that prevents cron expressions from firing more frequently than a configured threshold. This is useful for safeguarding against overly aggressive schedules in production.

MinInterval accepts a time.Duration between 1 minute and 1 hour. When set, the parser computes the smallest gap between consecutive fire times within a day and compares it against the limit. A zero value (default) disables the check.

There are two modes of enforcement, controlled by MinIntervalCorrection:

MinIntervalCorrection Behavior on violation
false (default) Parse returns an error
true The schedule is silently corrected to the min-interval rate
// Reject expressions that fire more often than every 5 minutes
parser := &cron.Parser{
    MinInterval: 5 * time.Minute,
}
schedule, err := parser.Parse("*/3 * * * *")
// err: "cron fires every 3m, which is more frequent than the minimum interval 5m"

// Alternatively, correct the schedule to the minimum interval instead of erroring
parser := &cron.Parser{
    MinInterval:           10 * time.Minute,
    MinIntervalCorrection: true,
}
schedule, err := parser.Parse("* * * * *")
// No error; the schedule now fires every 10 minutes instead of every minute
WASM/TinyGo Support

This library is fully compatible with TinyGo and WASM. Build for WASM:

tinygo build -target wasm -o cron.wasm ./cmd/wasm

Example WASM usage in JavaScript:

// Load the WASM module
const go = new Go();
WebAssembly.instantiateStreaming(fetch("cron.wasm"), go.importObject)
    .then((result) => {
        go.run(result.instance);

        // Reusable schedule: parse once, call next() many times
        const spec = cronParse("0 9 * * mon-fri");
        if (spec.error) {
            console.error(spec.error);
        } else {
            const now = Temporal.Now.instant();
            const r1 = spec.next(now.epochMilliseconds);
            console.log("Next run:", Temporal.Instant.fromEpochMilliseconds(r1));
            const r2 = spec.next(r1);
            console.log("Run after:", Temporal.Instant.fromEpochMilliseconds(r2));
            spec.free(); // release Go handles when done
        }

        // One-shot helper
        const r = cronNext("0 9 * * mon-fri", Temporal.Now.instant().epochMilliseconds);
        if (r.error) {
            console.error(r.error);
        } else {
            console.log("Next run:", Temporal.Instant.fromEpochMilliseconds(r));
        }
    });

Both functions accept and return Unix timestamps in milliseconds (number / integer). Use Temporal.Now.instant().epochMilliseconds to get the current time and Temporal.Instant.fromEpochMilliseconds(result.unixMillis) to convert the result back to a JS Temporal.Instant.

Syntax

[CRON_TZ=<timezone>] <min> <hour> <dom> <month> <dow>
  • Explanation to each field (from left to right):
Field Range
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
  • Special presentation (any, every, range and list)
* = any   */n = every n   a-b = range   a,b = list (OR)
  • When both day-of-month and day-of-week coexist, they act as OR condition
Example
Cron Expression Meaning
0 * * * * Every hour
0 9 * * mon-fri 9 AM on weekdays
*/15 * * * * Every 15 minutes
CRON_TZ=America/New_York 0 8 * * * 8 AM New York time daily
EBNF
Expression      = [ TimeZone, Space ], UnixExpr
TimeZone        = TimeZonePrefix, "=", IANATimeZone
TimeZonePrefix  = "CRON_TZ"
IANATimeZone    = (* IANA timezone identifier, e.g., "America/New_York", "UTC" *)
Space           = " " { " " } (* one or more U+0020 spaces *)

(* 5-field standard Unix cron: minute hour day-of-month month day-of-week *)
UnixExpr        = MinuteField, Space, HourField, Space, DayOfMonthField, Space, MonthField, Space, DayOfWeekField

(* Comma-separated field lists *)
MinuteField     = NumericFieldPart { ",", NumericFieldPart } (* 0-59 *)
HourField       = NumericFieldPart { ",", NumericFieldPart } (* 0-23 *)
DayOfMonthField = NumericFieldPart   { ",", NumericFieldPart }  (* 1-31 *)
MonthField      = MonthFieldPart   { ",", MonthFieldPart }  (* 1-12 *)
DayOfWeekField  = DOWFieldPart     { ",", DOWFieldPart }  (* 0-6 *)

(* Core field components: *, */N, N, N-N, N/N, N-N/N *)
NumericFieldPart = EveryStep [ "/" number ] | number [ "-" number ] [ "/" number ]
MonthFieldPart   = EveryStep [ "/" number ] | MonthValue [ "-" MonthValue ] [ "/" number ]
DOWFieldPart     = EveryStep [ "/" number ] | DOWValue [ "-" DOWValue ] [ "/" number ]

EveryStep       = "*"
number          = digit { digit }
digit           = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

(* Named values for months and days of week (case-insensitive in practice) *)
MonthValue      = number | "jan" | "feb" | "mar" | "apr" | "may" | "jun" | "jul" | "aug" | "sep" | "oct" | "nov" | "dec"
DOWValue        = number | "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Parser

type Parser struct {
	// YearLimit is the maximum number of years to search for the next schedule.
	// Default is 5 years if not set or set to 0.
	YearLimit int

	// MinInterval is the minimum allowed interval between consecutive cron fires.
	// If set, the parser will validate that the cron expression does not fire more
	// frequently than this duration. Must be between 1m and 1h (inclusive).
	// A zero value disables minimum interval checking.
	MinInterval time.Duration

	// MinIntervalCorrection controls behavior when a cron expression fires more
	// frequently than MinInterval.
	// If true, the schedule is corrected to fire at the MinInterval rate instead of
	// returning an error. If false (default), Parse returns an error.
	MinIntervalCorrection bool
}

Parser is a cron expression parser with configurable options. Parser is safe for concurrent use and can be reused across multiple Parse calls. It is compatible with TinyGo and WASM environments.

func NewParser

func NewParser(yearLimit int) *Parser

NewParser creates a new Parser with the specified year limit. If yearLimit is 0 or negative, the default of 5 years will be used.

Example:

parser := cron.NewParser(10) // Search up to 10 years ahead
schedule, err := parser.Parse("0 9 * * mon-fri")

func (*Parser) Parse

func (p *Parser) Parse(spec string) (Schedule, error)

Parse parses a 5-field cron expression and returns a schedule that can compute the next matching activation time using the parser's configuration.

type Schedule

type Schedule interface {
	// Next returns the next activation time, later than the given time.
	// Next is invoked initially, and then each time the job is run.
	Next(time.Time) time.Time
}

Schedule describes a job's duty cycle.

func Parse

func Parse(spec string) (Schedule, error)

Parse parses a 5-field cron expression and returns a schedule that can compute the next matching activation time. This is a convenience function that uses the default Parser configuration (5 year limit).

For custom configuration, create a Parser with NewParser() or use a Parser literal.

Example:

schedule, err := cron.Parse("0 9 * * mon-fri")
if err != nil {
    log.Fatal(err)
}
next := schedule.Next(time.Now())

type SpecSchedule

type SpecSchedule struct {
	Second, Minute, Hour, Dom, Month, Dow uint64

	// Override location for this schedule.
	Location  *time.Location
	YearLimit int
}

SpecSchedule specifies a duty cycle (to the second granularity), based on a traditional crontab specification. It is computed initially and stored as bit sets.

func (*SpecSchedule) Next

func (s *SpecSchedule) Next(t time.Time) time.Time

Next returns the next time this schedule is activated, greater than the given time. If no time can be found to satisfy the schedule, return the zero time.

Directories

Path Synopsis
cmd
wasm command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL