Skip to content
forked from robfig/cron

Maintained fork of robfig/cron - Go cron job scheduler with panic fixes, DST handling, and modern toolchain

License

Notifications You must be signed in to change notification settings

netresearch/go-cron

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

359 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Go Reference CI codecov CodeQL OpenSSF Scorecard OpenSSF Best Practices Go Report Card Go Version Latest Release License: MIT Contributor Covenant SLSA 3

go-cron

A maintained fork of robfig/cron — the most popular cron library for Go — with critical bug fixes, DST handling improvements, and modern toolchain support.

Why?

The original robfig/cron has been unmaintained since 2020, accumulating 50+ open PRs and several critical panic bugs that affect production systems. Rather than waiting indefinitely, this fork provides:

Issue Original This Fork
TZ= parsing panics Crashes on malformed input Fixed (#554, #555)
Chain decorators Entry.Run() bypasses chains Properly invokes wrappers (#551)
DST spring-forward Jobs silently skipped Runs immediately (ISC behavior, #541)
DOM/DOW logic OR (confusing) AND (logical, consistent)
Go version Stuck on 1.13 Go 1.25+ with modern toolchain

Installation

go get github.com/netresearch/go-cron
import cron "github.com/netresearch/go-cron"

Note

Requires Go 1.25 or later.

Migrating from robfig/cron

Drop-in replacement — just change the import path:

// Before
import "github.com/robfig/cron/v3"

// After
import cron "github.com/netresearch/go-cron"

The API is 100% compatible with robfig/cron v3. However, this fork includes knowingly accepted behavior changes that fix bugs and inconsistencies in the unmaintained upstream — see the comparison table above for a summary.

Warning

Behavior differences exist. While the API is compatible, some runtime behavior has changed (DOM/DOW matching, DST handling, chain execution). Review docs/MIGRATION.md before upgrading production systems.

Quick Start

package main

import (
    "fmt"
    "time"

    cron "github.com/netresearch/go-cron"
)

func main() {
    c := cron.New()

    // Run every minute
    c.AddFunc("* * * * *", func() {
        fmt.Println("Every minute:", time.Now())
    })

    // Run at specific times
    c.AddFunc("30 3-6,20-23 * * *", func() {
        fmt.Println("In the range 3-6am, 8-11pm")
    })

    // With timezone
    c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() {
        fmt.Println("4:30 AM Tokyo time")
    })

    c.Start()

    // Keep running...
    select {}
}

Cron Expression Format

Standard 5-field cron format (minute-first):

Field Required Values Special Characters
Minutes Yes 0-59 * / , -
Hours Yes 0-23 * / , -
Day of month Yes 1-31 * / , - ?
Month Yes 1-12 or JAN-DEC * / , -
Day of week Yes 0-6 or SUN-SAT * / , - ?

Predefined Schedules

Entry Description Equivalent
@yearly Once a year, midnight, Jan 1 0 0 1 1 *
@monthly Once a month, midnight, first day 0 0 1 * *
@weekly Once a week, midnight Sunday 0 0 * * 0
@daily Once a day, midnight 0 0 * * *
@hourly Once an hour, beginning of hour 0 * * * *
@every <duration> Every interval e.g., @every 1h30m

Wraparound Ranges

For cyclic fields, ranges where start > end wrap around the boundary:

// Run from 10pm to 2am (spans midnight)
c.AddFunc("0 22-2 * * *", nightJob)

// Run Friday through Monday (spans weekend)
c.AddFunc("0 9 * * FRI-MON", weekendJob)

// Run November through February (spans year boundary)
c.AddFunc("0 0 1 NOV-FEB *", winterJob)

Supported fields: seconds, minutes, hours, day-of-month, day-of-week, month. Non-existent days (e.g., Feb 31) are simply skipped.

Seconds Field (Optional)

Enable Quartz-compatible seconds field:

// Seconds field required
cron.New(cron.WithSeconds())

// Seconds field optional
cron.New(cron.WithParser(cron.NewParser(
    cron.SecondOptional | cron.Minute | cron.Hour |
    cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
)))

Day Matching (DOM/DOW)

When both day-of-month and day-of-week are specified, both must match (AND logic). This is consistent with all other cron fields and enables useful patterns:

// Last Friday of month (days 25-31 AND Friday)
c.AddFunc("0 0 25-31 * FRI", lastFridayJob)

// First Monday of month (days 1-7 AND Monday)
c.AddFunc("0 0 1-7 * MON", firstMondayJob)

// Friday the 13th
c.AddFunc("0 0 13 * FRI", unluckyJob)

Note

This differs from robfig/cron which uses OR logic. For migration compatibility, use the DowOrDom option or see docs/MIGRATION.md.

Timezone Support

Specify timezone per-schedule using CRON_TZ= prefix:

// Runs at 6am New York time
c.AddFunc("CRON_TZ=America/New_York 0 6 * * *", myFunc)

// Legacy TZ= prefix also supported
c.AddFunc("TZ=Europe/Berlin 0 9 * * *", myFunc)

Or set default timezone for all jobs:

nyc, _ := time.LoadLocation("America/New_York")
c := cron.New(cron.WithLocation(nyc))

Daylight Saving Time (DST) Handling

This library implements ISC cron-compatible DST behavior:

Transition Behavior
Spring Forward (hour skipped) Jobs in skipped hour run immediately after transition
Fall Back (hour repeats) Jobs run once, during first occurrence
Midnight DST (midnight doesn't exist) Automatically normalized to valid time

Tip

For DST-sensitive applications, schedule jobs outside typical transition hours (1-3 AM) or use UTC.

See docs/DST_HANDLING.md for comprehensive DST documentation including examples, testing strategies, and edge cases.

Job Wrappers (Middleware)

Add cross-cutting behavior using chains:

// Apply to all jobs
c := cron.New(cron.WithChain(
    cron.Recover(logger),              // Recover panics
    cron.SkipIfStillRunning(logger),   // Skip if previous still running
))

// Apply to specific job
job := cron.NewChain(
    cron.DelayIfStillRunning(logger),  // Queue if previous still running
).Then(myJob)

Available wrappers:

  • Recover — Catch panics, log, and continue
  • SkipIfStillRunning — Skip execution if previous run hasn't finished
  • DelayIfStillRunning — Queue execution until previous run finishes

Logging

Compatible with go-logr/logr:

// Verbose logging
c := cron.New(cron.WithLogger(
    cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags)),
))

Missed Job Catch-Up

Handle jobs that were missed while the scheduler was not running (e.g., application restart):

// Load last run time from your database
lastRun := loadFromDatabase("daily-report")

c.AddFunc("0 9 * * *", dailyReport,
    cron.WithPrev(lastRun),                      // When it last ran
    cron.WithMissedPolicy(cron.MissedRunOnce),   // Run once if missed
    cron.WithMissedGracePeriod(2*time.Hour),     // Only if within 2 hours
)

Policies:

  • MissedSkip (default) — No catch-up, wait for next scheduled time
  • MissedRunOnce — Run once immediately for the most recent missed execution
  • MissedRunAll — Run for every missed execution (capped at 100 for safety)

Important

The scheduler does NOT persist state. You must provide the last run time via WithPrev() and store it yourself (database, file, etc.). See docs/PERSISTENCE_GUIDE.md for complete integration patterns.

API Reference

Full documentation: pkg.go.dev/github.com/netresearch/go-cron

Contributing

Contributions are welcome! Please read CONTRIBUTING.md before submitting PRs.

Security

For security issues, please see SECURITY.md.

License

MIT License — see LICENSE for details.


This fork is maintained by Netresearch. The original cron library was created by Rob Figueiredo.

About

Maintained fork of robfig/cron - Go cron job scheduler with panic fixes, DST handling, and modern toolchain

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Languages

  • Go 98.9%
  • Other 1.1%