cron

package module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 15 Imported by: 0

README

cron

Doc Go Release Test Report Card Stars License

A modern, focused Go cron scheduler with no third-party dependencies.

Features

  • Standard 5-field cron expressions plus @hourly / @daily / @every 10s descriptors and a per-spec TZ= prefix.
  • Optional seconds field. WithSeconds() accepts both 5 and 6 fields; WithSeconds(true) requires 6.
  • Quartz tokens (L, N#M, NL) via the parserext subpackage.
  • DAG jobs with conditional dependencies via the workflow subpackage.
  • Job wrappers in wrap: Recover, Timeout, Retry, SkipIfRunning, DelayIfRunning.
  • Per-event hooks and recorders so you can plug in metrics and tracing.
  • Missed-fire policies (MissedSkip, MissedRunOnce) with a configurable tolerance window for in-process stalls (VM suspend, clock jumps, backlog).
  • Manual Trigger and TriggerByName, with concurrency and entry limits.
  • DST-aware. Per-entry timeout, jitter, retry, name, and chain.

Install

go get github.com/libtnb/cron

Requires Go 1.25+ (uses iter.Seq, sync.WaitGroup.Go, and slog).

Quick start

package main

import (
	"context"
	"fmt"
	"log/slog"
	"os/signal"
	"syscall"
	"time"

	"github.com/libtnb/cron"
	"github.com/libtnb/cron/wrap"
)

func main() {
	// Cancel on SIGINT / SIGTERM.
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	// Build a scheduler. Wrappers in WithChain apply to every entry.
	c := cron.New(
		cron.WithLogger(slog.Default()),
		cron.WithChain(wrap.Recover(), wrap.Timeout(30*time.Second)),
	)

	// Add a job. Add returns an EntryID and a parse error (if any).
	_, err := c.Add("@every 5s", cron.JobFunc(func(ctx context.Context) error {
		fmt.Println("tick", time.Now())
		return nil
	}), cron.WithName("heartbeat"))
	if err != nil {
		panic(err)
	}

	// Start the loop. Idempotent while running.
	if err := c.Start(); err != nil {
		panic(err)
	}

	<-ctx.Done()

	// Drain in-flight jobs and hooks. The deadline caps the wait.
	shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	_ = c.Stop(shutdownCtx)
}

Subpackages

Path Purpose
github.com/libtnb/cron Scheduler, parser, schedules, hooks, recorders, retry policy.
github.com/libtnb/cron/wrap Job wrappers: Recover, Timeout, SkipIfRunning, DelayIfRunning, Retry.
github.com/libtnb/cron/workflow DAG jobs with OnSuccess, OnFailure, OnSkipped, OnComplete.
github.com/libtnb/cron/parserext Quartz tokens (L, N#M, NL).

Cron expressions

The default parser takes five fields:

minute hour day-of-month month day-of-week

Names are accepted (mon, MON, mon-fri, jan). Step (*/5), range (1-5), list (15,45), and combinations are supported. A spec may carry a TZ=Europe/Berlin or CRON_TZ=... prefix to override the scheduler's timezone for that entry.

The descriptors @yearly, @monthly, @weekly, @daily, @midnight, @hourly, and @every <duration> are also accepted. @every 90s is the canonical fixed-interval form; the interval has a 1s floor.

To use seconds, enable it on the built-in parser (keeps WithLocation working):

cron.WithSecondsField()

Or install a custom parser, which then owns the timezone:

// Optional seconds: 5- and 6-field specs both parse.
cron.WithParser(cron.NewStandardParser(cron.WithSeconds()))

// Strict: 6 fields required.
cron.WithParser(cron.NewStandardParser(cron.WithSeconds(true)))

Building a scheduler

c := cron.New(
	cron.WithLocation(time.UTC),
	cron.WithSecondsField(), // the spec below has a seconds field
	cron.WithMissedFire(cron.MissedRunOnce),
	cron.WithMaxConcurrent(32),
	cron.WithRetry(cron.Retry(3, cron.RetryInitial(time.Second))),
)

id, err := c.Add(
	"0 0 9 * * *",
	emailJob,
	cron.WithName("daily-digest"),
	cron.WithTimeout(time.Minute),
)

AddSchedule registers a programmatic Schedule instead of a string:

id, err := c.AddSchedule(cron.ConstantDelay(time.Hour), job)
Missed fires

When a firing runs more than WithMissedTolerance (default 1s) late, WithMissedFire decides what to do:

  • MissedSkip (default) drops the missed firing and waits for the next scheduled time.
  • MissedRunOnce runs the job once at the most recent missed time, then resumes the regular schedule. This covers in-process stalls (VM suspend, clock jumps, a backlog while the loop was blocked) — not restarts: a fresh process has no record of firings missed while it was down.

Triggering and removal

Trigger runs the job immediately. The returned error tells the caller why dispatch was rejected:

if err := c.Trigger(id); err != nil {
	switch {
	case errors.Is(err, cron.ErrEntryNotFound):
	case errors.Is(err, cron.ErrSchedulerNotRunning):
	case errors.Is(err, cron.ErrConcurrencyLimit):
	}
}

count, err := c.TriggerByName("daily-digest") // err joins per-entry failures

c.Remove(id) // false if id is unknown

Remove blocks future automatic fires and future Trigger calls for that entry. Jobs already dispatched keep running. Stop halts the loop and waits for in-flight jobs and the hook dispatcher, capped by the context.

Reading entries

Entry and Entries return copies and never block on the scheduler's internal lock, so they are safe to call from a hot path (HTTP handler, debug endpoint).

if entry, ok := c.Entry(id); ok {
	fmt.Println(entry.Name, entry.Next)
}

for e := range c.Entries() {
	fmt.Println(e.Name, e.Prev, e.Next)
}

NextN and Between operate on a Schedule directly, without a running scheduler:

next := cron.NextN(schedule, time.Now(), 10)
window := cron.Between(schedule, start, end)

Hooks and recorders

Hooks and recorders are split per event so a subscriber implements only the methods it cares about:

  • Hooks: ScheduleHook, JobStartHook, JobCompleteHook, MissedHook.
  • Recorders: JobScheduledRecorder, JobStartedRecorder, JobCompletedRecorder, JobMissedRecorder, QueueDepthRecorder, HookDroppedRecorder.
type metrics struct{}

// Implements JobCompleteHook only; the other 3 events are skipped automatically.
func (*metrics) OnJobComplete(e cron.EventJobComplete) {
	// record duration, error, etc.
}

c := cron.New(cron.WithHooks(&metrics{}))

Hooks are delivered on a buffered channel and dropped when the buffer is full. The size is configurable via WithHookBuffer and the drop count is exposed through HookDroppedRecorder.

Recorders, unlike hooks, are not serialized: their methods are called inline and concurrently from job goroutines, the scheduler loop, and Add/Remove/Trigger callers. Implementations must be concurrency-safe and non-blocking.

Workflow DAGs

workflow.Workflow is a cron.Job, so a DAG can be scheduled with Add or AddSchedule like any other job. workflow.New validates the graph and returns an error (ErrDuplicateStep, ErrUnknownDep, ErrCycle); workflow.MustNew panics on misconfiguration and is convenient for static graphs.

w := workflow.MustNew(
	workflow.NewStep("download", downloadJob),
	workflow.NewStep("transform", transformJob,
		workflow.After("download", workflow.OnSuccess)),
	workflow.NewStep("notify_failure", notifyJob,
		workflow.After("transform", workflow.OnFailure)),
)
_, _ = c.Add("@hourly", w, cron.WithName("etl"))

Conditions: OnSuccess, OnFailure, OnSkipped, OnComplete (any terminal state). A step is skipped when one of its dependencies didn't match the requested condition.

Quartz tokens

parserext.NewQuartzParser accepts standard 5/6-field specs and adds L (last day of month), N#M (Nth weekday of month), and NL (last weekday of month).

c := cron.New(cron.WithParser(parserext.NewQuartzParser(time.UTC)))

_, _ = c.Add("0 0 18 L * ?", reportJob)    // last day of every month
_, _ = c.Add("0 0 9 ? * 5#3", standupJob)  // third Friday
_, _ = c.Add("0 30 22 ? * 5L", payrollJob) // last Friday

? is accepted in the day-of-month and day-of-week fields per the Quartz convention.

Migrating from robfig/cron

robfig/cron libtnb/cron
cron.New(cron.WithSeconds()) cron.New(cron.WithSecondsField())
Job.Run() Job.Run(context.Context) error
c.AddFunc(spec, func()) c.Add(spec, cron.JobFunc(func(ctx) error { ... }))
cron.WithLogger(custom) cron.WithLogger(*slog.Logger)
cron.Recover(logger) wrap.Recover(wrap.WithLogger(logger))
cron.SkipIfStillRunning(logger) wrap.SkipIfRunning()
cron.DelayIfStillRunning(logger) wrap.DelayIfRunning()
c.Start() c.Start() error
c.Stop() c.Stop(ctx) error
c.Entries() c.Entries() iter.Seq[Entry]

Credits

Documentation

Overview

Package cron is a cron scheduler.

Register jobs with Add or AddSchedule, then call Start. Stop cancels the loop and waits for in-flight jobs, capped by its context.

Specs use five fields. WithSeconds adds an optional leading seconds field; WithSeconds(true) requires six.

Subpackages

  • wrap: job wrappers (Recover, Timeout, SkipIfRunning, DelayIfRunning, Retry).
  • workflow: DAG jobs with conditional dependencies.
  • parserext: Quartz tokens (L, N#M, NL).

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrCapacityReached     = errors.New("cron: capacity reached")
	ErrAlreadyRunning      = errors.New("cron: job already running")
	ErrJobTimeout          = errors.New("cron: job timeout")
	ErrCronStopping        = errors.New("cron: scheduler stopping")
	ErrEntryNotFound       = errors.New("cron: entry not found")
	ErrSchedulerNotRunning = errors.New("cron: scheduler not running")
	ErrConcurrencyLimit    = errors.New("cron: max concurrent reached")
	ErrSchedulerStopped    = errors.New("cron: scheduler stopped")
	ErrNilJob              = errors.New("cron: nil job")
	ErrNilSchedule         = errors.New("cron: nil schedule")
)

Functions

func Between

func Between(s Schedule, start, end time.Time) []time.Time

Between returns every firing in (start, end].

func IsTriggered

func IsTriggered(s Schedule) bool

IsTriggered reports whether s came from TriggeredSchedule.

func NextN

func NextN(s Schedule, from time.Time, n int) []time.Time

NextN returns the next n firings strictly after from.

func ValidateSpec

func ValidateSpec(spec string) error

ValidateSpec returns nil iff spec parses with the standard parser.

func ValidateSpecWith

func ValidateSpecWith(spec string, p Parser) error

ValidateSpecWith is ValidateSpec with a custom parser.

Types

type ConstantDelay

type ConstantDelay time.Duration

ConstantDelay is a fixed-interval Schedule. The interval has a 1s floor: sub-second durations fire every 1s. Whole-second intervals are aligned to the second boundary; fractional intervals (e.g. 1500ms) keep their exact period.

func (ConstantDelay) Next

func (d ConstantDelay) Next(now time.Time) time.Time

func (ConstantDelay) String

func (d ConstantDelay) String() string

type Cron

type Cron struct {
	// contains filtered or unexported fields
}

Cron is a job scheduler. Construct one with New, register jobs, then Start.

func New

func New(opts ...Option) *Cron

New constructs a Cron. It does not start scheduling until Start is called.

func (*Cron) Add

func (c *Cron) Add(spec string, j Job, opts ...EntryOption) (EntryID, error)

Add parses spec and registers j. It returns a *ParseError for invalid specs or ErrCapacityReached when WithMaxEntries rejects the registration.

func (*Cron) AddSchedule

func (c *Cron) AddSchedule(s Schedule, j Job, opts ...EntryOption) (EntryID, error)

AddSchedule registers j against a programmatic Schedule.

func (*Cron) Entries

func (c *Cron) Entries() iter.Seq[Entry]

Entries returns registered entry snapshots ordered by Next.

func (*Cron) Entry

func (c *Cron) Entry(id EntryID) (Entry, bool)

Entry returns the current snapshot for id.

func (*Cron) Remove

func (c *Cron) Remove(id EntryID) bool

Remove deregisters id. In-flight invocations continue; future automatic fires and future Trigger calls for id are rejected.

func (*Cron) Running

func (c *Cron) Running() bool

Running reports whether the scheduler is running. It is observational; use Trigger's returned error for race-free dispatch decisions.

func (*Cron) Start

func (c *Cron) Start() error

Start launches the scheduler. It is idempotent while running and returns ErrSchedulerStopped after Stop has been called.

func (*Cron) Stop

func (c *Cron) Stop(ctx context.Context) error

Stop halts the scheduler and waits for the loop, in-flight jobs and hook dispatcher to drain, capped by ctx. Returns ctx.Err() on timeout. Do not call it from inside a Job.

func (*Cron) Trigger

func (c *Cron) Trigger(id EntryID) error

Trigger fires id immediately. It returns ErrSchedulerNotRunning, ErrEntryNotFound, or ErrConcurrencyLimit when dispatch is rejected.

func (*Cron) TriggerByName

func (c *Cron) TriggerByName(name string) (int, error)

TriggerByName fires every entry whose Name matches name. Returns the successful dispatch count and errors.Join of per-Trigger failures. No match returns (0, nil); not running returns (0, ErrSchedulerNotRunning).

type Entry

type Entry struct {
	ID       EntryID
	Name     string
	Spec     string // empty for AddSchedule entries
	Schedule Schedule
	Prev     time.Time // zero if never fired
	Next     time.Time // zero if exhausted or TriggeredSchedule
}

Entry is the public read-only view of a scheduled item. Safe to copy.

func (Entry) LogValue

func (e Entry) LogValue() slog.Value

func (Entry) Valid

func (e Entry) Valid() bool

Valid reports whether e refers to a registered entry. Zero is invalid.

type EntryID

type EntryID uint64

EntryID is an opaque, process-local identifier.

func (EntryID) LogValue

func (id EntryID) LogValue() slog.Value

func (EntryID) String

func (id EntryID) String() string

type EntryOption

type EntryOption func(*entryConfig)

EntryOption configures one entry.

func WithEntryChain

func WithEntryChain(wrappers ...Wrapper) EntryOption

WithEntryChain installs per-entry wrappers inside the global chain.

func WithEntryRetry

func WithEntryRetry(p RetryPolicy) EntryOption

WithEntryRetry overrides the global retry for one entry. A zero policy disables retry for that entry.

func WithName

func WithName(name string) EntryOption

WithName labels an entry.

func WithTimeout

func WithTimeout(d time.Duration) EntryOption

WithTimeout caps a Job's runtime with ErrJobTimeout as the cancel cause.

type EventJobComplete

type EventJobComplete struct {
	EntryID     EntryID
	Name        string
	ScheduledAt time.Time
	FireAt      time.Time
	Duration    time.Duration
	Err         error
}

EventJobComplete is emitted after the chain returns. Err is the chain result.

type EventJobStart

type EventJobStart struct {
	EntryID     EntryID
	Name        string
	ScheduledAt time.Time
	FireAt      time.Time
}

EventJobStart is emitted just before the chain runs. ScheduledAt is the schedule-selected time; FireAt is the actual start time after jitter/queueing.

type EventMissed

type EventMissed struct {
	EntryID     EntryID
	Name        string
	ScheduledAt time.Time
	Lateness    time.Duration
	Policy      MissedFirePolicy
}

EventMissed is emitted for missed fires and MaxConcurrent rejections.

type EventSchedule

type EventSchedule struct {
	EntryID  EntryID
	Name     string
	Schedule Schedule
	Next     time.Time
}

EventSchedule is emitted when an entry is added or its next firing is recomputed after a fire.

type HookDroppedRecorder

type HookDroppedRecorder interface{ HookDropped() }

type Job

type Job interface {
	Run(ctx context.Context) error
}

Job is the unit of work executed by the scheduler.

type JobCompleteHook

type JobCompleteHook interface{ OnJobComplete(EventJobComplete) }

type JobCompletedRecorder

type JobCompletedRecorder interface {
	JobCompleted(name string, dur time.Duration, err error)
}

type JobFunc

type JobFunc func(ctx context.Context) error

JobFunc adapts a function to Job.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/cron"
)

func main() {
	j := cron.JobFunc(func(ctx context.Context) error {
		fmt.Println("hello")
		return nil
	})
	_ = j.Run(context.Background())
}
Output:
hello

func (JobFunc) Run

func (f JobFunc) Run(ctx context.Context) error

type JobMissedRecorder

type JobMissedRecorder interface {
	JobMissed(name string, lateness time.Duration)
}

type JobScheduledRecorder

type JobScheduledRecorder interface{ JobScheduled(name string) }

type JobStartHook

type JobStartHook interface{ OnJobStart(EventJobStart) }

type JobStartedRecorder

type JobStartedRecorder interface{ JobStarted(name string) }

type MissedFirePolicy

type MissedFirePolicy uint8

MissedFirePolicy controls behaviour when a fire is later than WithMissedTolerance. OnMissedFire fires regardless of policy.

const (
	// MissedSkip ignores missed firings and resumes from the next
	// scheduled time. This is the default.
	MissedSkip MissedFirePolicy = iota

	// MissedRunOnce runs the job once for the most recent missed firing
	// (latest schedule.Next <= now), then resumes normally.
	MissedRunOnce
)

func (MissedFirePolicy) String

func (p MissedFirePolicy) String() string

type MissedHook

type MissedHook interface{ OnMissedFire(EventMissed) }

type Option

type Option func(*config)

Option configures a Cron.

func WithChain

func WithChain(wrappers ...Wrapper) Option

WithChain installs global wrappers. First wrapper is outermost.

func WithHookBuffer

func WithHookBuffer(n int) Option

WithHookBuffer sets the hook event buffer size. Full buffers drop new events.

func WithHooks

func WithHooks(hooks ...any) Option

WithHooks installs async hook subscribers. Values may implement any subset of ScheduleHook, JobStartHook, JobCompleteHook, and MissedHook.

func WithJitter

func WithJitter(max time.Duration) Option

WithJitter adds a random delay in [0, max) to each firing.

func WithLocation

func WithLocation(loc *time.Location) Option

WithLocation sets the default schedule timezone. Default is time.Local. Ignored when WithParser is set: a custom parser owns its timezone.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the slog.Logger. Default slog.Default().

func WithMaxConcurrent

func WithMaxConcurrent(n int) Option

WithMaxConcurrent caps in-flight jobs. Zero means unlimited.

func WithMaxEntries

func WithMaxEntries(n int) Option

WithMaxEntries caps registered entries. Zero means unlimited.

func WithMissedFire

func WithMissedFire(p MissedFirePolicy) Option

WithMissedFire selects the missed-fire policy. Default MissedSkip.

func WithMissedTolerance

func WithMissedTolerance(d time.Duration) Option

WithMissedTolerance sets the lateness threshold for "missed". Default 1s.

func WithParser

func WithParser(p Parser) Option

WithParser installs a parser. It takes over timezone resolution, so WithLocation and WithSecondsField no longer apply.

func WithRecorder

func WithRecorder(r any) Option

WithRecorder installs a metrics subscriber. Values may implement any subset of the recorder sub-interfaces.

func WithRetry

func WithRetry(p RetryPolicy) Option

WithRetry sets the default RetryPolicy. Overridden by WithEntryRetry.

func WithSecondsField added in v0.2.2

func WithSecondsField() Option

WithSecondsField enables a leading seconds field in the built-in parser, so the common seconds + WithLocation case composes without WithParser.

type ParseError

type ParseError struct {
	Spec   string
	Field  string // e.g. "minute"; "" if not applicable
	Pos    int    // 0-based byte offset; -1 if unknown
	Reason string
	Err    error
}

ParseError describes a failure parsing a cron specification.

func (*ParseError) Error

func (e *ParseError) Error() string

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

type Parser

type Parser interface {
	Parse(spec string) (Schedule, error)
}

Parser turns a textual spec into a Schedule. Cron caches parser results.

type ParserOption

type ParserOption func(*parserConfig)

ParserOption configures NewStandardParser.

func WithDefaultLocation

func WithDefaultLocation(loc *time.Location) ParserOption

WithDefaultLocation sets the default timezone for specs without TZ=/CRON_TZ=. nil means time.Local.

func WithParserExt

func WithParserExt(ext Parser) ParserOption

WithParserExt installs a pre-parse hook. Returning (nil, nil) falls through to the standard parser.

func WithSeconds

func WithSeconds(strict ...bool) ParserOption

WithSeconds enables a leading seconds field. By default the parser accepts both 5- and 6-field specs (a 5-field spec is parsed with second=0). Pass true to require exactly 6 fields.

type QueueDepthRecorder

type QueueDepthRecorder interface{ QueueDepth(n int) }

type RetryOption

type RetryOption func(*RetryPolicy)

RetryOption configures a RetryPolicy built by Retry.

func RetryInitial

func RetryInitial(d time.Duration) RetryOption

RetryInitial sets the first retry delay (default 1s).

func RetryJitterFrac

func RetryJitterFrac(f float64) RetryOption

RetryJitterFrac is fractional uniform jitter (e.g. 0.1 = ±10%).

func RetryMaxDelay

func RetryMaxDelay(d time.Duration) RetryOption

RetryMaxDelay caps backoff (zero = uncapped).

func RetryMultiplier

func RetryMultiplier(m float64) RetryOption

RetryMultiplier is the per-attempt growth factor (<=1 stays constant).

type RetryPolicy

type RetryPolicy struct {
	MaxRetries int
	Initial    time.Duration
	MaxDelay   time.Duration
	Multiplier float64
	JitterFrac float64
}

RetryPolicy describes exponential backoff with optional jitter. MaxRetries == 0 disables retry; negative means unlimited until ctx cancellation. Fields are exported for config-driven assembly; use Retry(...) for programmatic construction.

func Retry

func Retry(maxRetries int, opts ...RetryOption) RetryPolicy

Retry builds a RetryPolicy. maxRetries is the number of retries after the initial attempt; negative retries until ctx cancellation.

func (RetryPolicy) IsZero

func (p RetryPolicy) IsZero() bool

IsZero is keyed only on MaxRetries so half-filled policies (e.g. only Initial set) don't produce a useless wrapper.

func (RetryPolicy) Wrapper

func (p RetryPolicy) Wrapper() Wrapper

Wrapper returns a Wrapper that retries on error per p. Attempt errors are joined via errors.Join; ctx cancellation aborts, recording context.Cause so ErrJobTimeout / ErrCronStopping survive into the joined error.

type Schedule

type Schedule interface {
	Next(now time.Time) time.Time
}

Schedule yields successive firing times. Next must return the first firing strictly after now, or zero when exhausted.

func TriggeredSchedule

func TriggeredSchedule() Schedule

TriggeredSchedule never fires automatically. Combine with Trigger.

type ScheduleHook

type ScheduleHook interface{ OnSchedule(EventSchedule) }

Hook sub-interfaces. WithHooks subscribers may implement any subset.

type SpecAnalysis

type SpecAnalysis struct {
	Spec        string
	Valid       bool
	Err         error
	IsTriggered bool
	Descriptor  string         // "@every", "@hourly", ... or "" for 5/6-field specs
	Interval    time.Duration  // set when Descriptor == "@every"
	Location    *time.Location // schedule timezone
	NextRun     time.Time      // upcoming firing relative to the now passed in
}

SpecAnalysis is the result of AnalyzeSpec. Most fields are populated only when Valid is true.

func AnalyzeSpec

func AnalyzeSpec(spec string, now time.Time) SpecAnalysis

AnalyzeSpec parses spec and returns a structured description.

func AnalyzeSpecWith

func AnalyzeSpecWith(spec string, p Parser, now time.Time) SpecAnalysis

AnalyzeSpecWith is AnalyzeSpec with a custom parser.

type SpecSchedule

type SpecSchedule struct {
	// contains filtered or unexported fields
}

SpecSchedule is a parsed cron expression.

func (*SpecSchedule) Location

func (s *SpecSchedule) Location() *time.Location

Location returns the evaluation timezone.

func (*SpecSchedule) LogValue

func (s *SpecSchedule) LogValue() slog.Value

func (*SpecSchedule) Next

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

Next returns the next firing after t, or zero if none is found.

The hour branch reconstructs the wall clock at the target hour instead of adding an absolute (h-hour)*time.Hour, which would overshoot a DST spring-forward gap and skip the day. The minute/second branches keep their absolute jumps: they never straddle a whole-hour DST boundary, and stepping through the repeated hour on fall-back keeps both firings observable.

func (*SpecSchedule) Upcoming

func (s *SpecSchedule) Upcoming(from time.Time) iter.Seq[time.Time]

Upcoming is a lazy iterator over future firings.

type StandardParser

type StandardParser struct {
	// contains filtered or unexported fields
}

StandardParser is stateless and concurrent-safe.

func NewStandardParser

func NewStandardParser(opts ...ParserOption) *StandardParser

NewStandardParser handles 5/6-field specs, descriptors, and TZ prefixes.

func (*StandardParser) Parse

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

type Upcoming

type Upcoming interface {
	Upcoming(from time.Time) iter.Seq[time.Time]
}

Upcoming is an optional lazy iteration capability.

type Wrapper

type Wrapper func(Job) Job

Wrapper decorates a Job.

func Chain

func Chain(wrappers ...Wrapper) Wrapper

Chain composes wrappers so the first wraps outermost.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/cron"
)

func main() {
	mk := func(name string) cron.Wrapper {
		return func(j cron.Job) cron.Job {
			return cron.JobFunc(func(ctx context.Context) error {
				fmt.Println("enter", name)
				err := j.Run(ctx)
				fmt.Println("leave", name)
				return err
			})
		}
	}
	core := cron.JobFunc(func(ctx context.Context) error {
		fmt.Println("run core")
		return nil
	})
	_ = cron.Chain(mk("outer"), mk("inner"))(core).Run(context.Background())
}
Output:
enter outer
enter inner
run core
leave inner
leave outer

Directories

Path Synopsis
contrib
otel module
prometheus module
coordination
postgres module
redis module
examples
hello command
quartz command
seconds command
slog command
workflow command
internal
heap
Package heap provides a typed min-heap with addressable items.
Package heap provides a typed min-heap with addressable items.
parsecache
Package parsecache memoises parser results and never evicts.
Package parsecache memoises parser results and never evicts.
Package parserext provides optional cron parser extensions.
Package parserext provides optional cron parser extensions.
Package workflow runs DAGs of cron.Jobs.
Package workflow runs DAGs of cron.Jobs.
Package wrap supplies Job decorators.
Package wrap supplies Job decorators.

Jump to

Keyboard shortcuts

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