cron

package
v0.2.7 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

cron: the schedule grammar

pkg/cron is the schedule grammar of CSF's cron service: human-readable trigger declarations, their canonical five-field form, and the pure value model of triggers and occurrences that the scheduler, its store and the Liquid Proto contract share. It starts no goroutines and crosses no boundary. The scheduler that fires triggers and records occurrences is services/cron.

Declaring a schedule

Schedules are typed builders rather than strings. Each one normalizes to a canonical five-field cron form, and that string, not the builder call, is what a TriggerDefinition persists and a status snapshot reports. These pairs are pinned by schedule_test.go:

Declaration Canonical()
cron.Spec(cron.Daily(cron.At(3).PM())) 0 15 * * *
cron.Spec(cron.Weekly(time.Monday, cron.At(8, 30).AM())) 30 8 * * 1
cron.Spec(cron.Monthly(1, cron.Noon())) 0 12 1 * *
cron.Spec(cron.LastDayOfMonth(cron.Midnight())) 0 0 L * *
cron.Spec(cron.Every(15 * time.Minute)) @every 15m0s
cron.Spec(cron.Raw("*/15 2-3 * * 1,3")) */15 2-3 * * 1,3

Schedule.String() is the human rendering of the same declaration: the first row reads daily at 3:00 PM (UTC).

At returns a MeridiemTime, not a TimeOfDay, so a twelve-hour clock declaration cannot reach a schedule until it says which half of the day it means: the ambiguous spelling is a compile error rather than a trigger that fires twelve hours off.

cron.Daily(cron.At(3))        // does not compile: MeridiemTime is not a TimeOfDay
cron.Daily(cron.At(3).PM())   // 15:00
cron.Daily(cron.At24(15))     // the same instant on a 24-hour clock

Spec schedules in UTC. Schedule.In(location) returns a copy in another location, and neither builder panics: an invalid declaration is carried until Validate, Canonical, or Next reports it. An interval schedule is anchored once, at the instant it is first reconciled, so its cadence survives a restart; Schedule.Anchor states the anchor explicitly.

The value model

TriggerDefinition is the persistence-neutral declaration of one trigger: its name, which ValidateTriggerName holds to ^[a-z][a-z0-9._/-]*$ and 128 bytes, its ScheduleDefinition, and its CatchUpPolicy and OverlapPolicy. NormalizeTriggerDefinition validates one and anchors an interval schedule; PreserveIntervalAnchor keeps an established anchor across a re-declaration. TriggerState is a trigger's durable cursor, and OccurrenceRecord the durable record of one scheduled instant; OccurrenceID is its stable identity, the operation's idempotency key.

Contract

contract maps the value model to the validated Liquid Proto messages under v1: ScheduleSpec, TriggerDefinition, TriggerStatus and StatusSnapshot. They are HTTP and messaging boundary contracts; protobuf wire bytes are never stored in the database. Regenerate them with pkg/proto/generate.sh.

Documentation

Overview

Package cron is the schedule grammar: human-readable trigger declarations, their canonical five-field form, and the pure value model of triggers and occurrences that the scheduler, its store and the Liquid Proto contract share. It starts no goroutines and crosses no boundary; the service that fires triggers and records occurrences is candace/services/cron.

Index

Constants

View Source
const (
	// MaxTriggerNameBytes bounds a trigger name, as the store's check
	// constraint does.
	MaxTriggerNameBytes = 128
)

Variables

View Source
var ErrInvalidTrigger = errors.New("cron: invalid trigger")

ErrInvalidTrigger reports a trigger declaration the grammar rejects: its name, one of its policies or its schedule definition.

Functions

func NormalizeTriggerDefinition added in v0.2.0

func NormalizeTriggerDefinition(definition TriggerDefinition, now time.Time) (TriggerDefinition, Schedule, error)

NormalizeTriggerDefinition validates a declaration and returns it in its persisted form together with its schedule. An interval schedule that states no anchor is anchored at now, so its cadence survives a restart; an anchor is kept at PostgreSQL's microsecond precision.

func OccurrenceID

func OccurrenceID(triggerName string, scheduledAt time.Time) string

OccurrenceID is the stable identity of one trigger's scheduled instant, and so the idempotency key of the operation that runs it.

func ValidateTriggerName added in v0.2.0

func ValidateTriggerName(name string) error

ValidateTriggerName accepts the names the store's check constraint accepts: one to MaxTriggerNameBytes bytes of lower-case ASCII letters, digits, dots, underscores, slashes and dashes, starting with a letter.

Types

type CatchUpPolicy

type CatchUpPolicy string

CatchUpPolicy says what a trigger does with the occurrences it missed while its scheduler was not running.

const (
	// CatchUpNone advances past every missed occurrence without invoking it,
	// as traditional cron does. It is the default.
	CatchUpNone CatchUpPolicy = "none"
	// CatchUpLatest invokes only the most recent missed occurrence.
	CatchUpLatest CatchUpPolicy = "latest"
	// CatchUpAll invokes every missed occurrence, up to the scheduler's
	// catch-up limit; the overlap policy still applies while they run.
	CatchUpAll CatchUpPolicy = "all"
)

func (CatchUpPolicy) Valid added in v0.2.0

func (policy CatchUpPolicy) Valid() bool

Valid reports whether the policy is one the grammar defines.

type MeridiemTime

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

MeridiemTime is deliberately distinct from TimeOfDay. It can only become a schedule time after AM or PM is selected, preventing Daily(At(3)).

func At

func At(hour int, minute ...int) MeridiemTime

At starts a 12-hour clock declaration. It accepts an optional minute.

func (MeridiemTime) AM

func (value MeridiemTime) AM() TimeOfDay

AM completes a 12-hour declaration.

func (MeridiemTime) PM

func (value MeridiemTime) PM() TimeOfDay

PM completes a 12-hour declaration.

type OccurrenceRecord

type OccurrenceRecord struct {
	ID           string           `json:"id"`
	TriggerName  string           `json:"trigger_name"`
	ScheduledAt  time.Time        `json:"scheduled_at"`
	Status       OccurrenceStatus `json:"status"`
	Attempt      uint32           `json:"attempt"`
	StartedAt    time.Time        `json:"started_at,omitempty"`
	FinishedAt   time.Time        `json:"finished_at,omitempty"`
	LeaseOwner   string           `json:"lease_owner,omitempty"`
	LeaseToken   string           `json:"-"`
	LeaseUntil   time.Time        `json:"lease_until,omitempty"`
	Error        string           `json:"error,omitempty"`
	SkipReason   string           `json:"skip_reason,omitempty"`
	LastModified time.Time        `json:"last_modified"`
}

OccurrenceRecord is the durable execution record of one trigger's scheduled instant. LeaseToken is the fencing secret the store checks and is deliberately omitted from JSON snapshots.

type OccurrenceStatus

type OccurrenceStatus string

OccurrenceStatus is the durable state of one occurrence: running under a lease, or one of the four terminal states.

const (
	OccurrenceRunning   OccurrenceStatus = "running"
	OccurrenceSucceeded OccurrenceStatus = "succeeded"
	OccurrenceFailed    OccurrenceStatus = "failed"
	OccurrenceCanceled  OccurrenceStatus = "canceled"
	OccurrenceSkipped   OccurrenceStatus = "skipped"
)

func (OccurrenceStatus) Completion added in v0.2.0

func (status OccurrenceStatus) Completion() bool

Completion reports whether the status is one an operation's completion records: succeeded, failed or canceled. A skipped occurrence never ran.

type OverlapPolicy

type OverlapPolicy string

OverlapPolicy says whether two occurrences of one trigger may run at the same time. The store enforces it, so it holds across processes sharing one database, not only within one.

const (
	// OverlapSkip records a skipped occurrence while another occurrence of
	// the trigger holds a live lease. It is the default.
	OverlapSkip OverlapPolicy = "skip"
	// OverlapAllow lets occurrences of one trigger run concurrently.
	OverlapAllow OverlapPolicy = "allow"
)

func (OverlapPolicy) Valid added in v0.2.0

func (policy OverlapPolicy) Valid() bool

Valid reports whether the policy is one the grammar defines.

type Rule

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

Rule is an opaque schedule declaration constructed by the fluent helpers. It intentionally has no exported fields so a caller cannot construct an unvalidated wire-shaped schedule.

func Daily

func Daily(at TimeOfDay) Rule

Daily returns a rule that fires every local calendar day at at.

func Every

func Every(interval time.Duration) Rule

Every returns an interval rule. Its cadence is anchored when Anchor is supplied by the durable runtime; absent an explicit anchor, Next uses the Unix epoch as a stable default rather than process start time.

func LastDayOfMonth

func LastDayOfMonth(at TimeOfDay) Rule

LastDayOfMonth returns a rule that fires on each month's final local day.

func Monthly

func Monthly(day int, at TimeOfDay) Rule

Monthly returns a rule that fires on day (1 through 31). Months without that day are skipped.

func Raw

func Raw(expression string) Rule

Raw declares a five-field cron expression. Raw is an escape hatch; it is parsed and normalized by Spec/Validate, never persisted as a wire blob.

func Weekly

func Weekly(day time.Weekday, at TimeOfDay) Rule

Weekly returns a rule that fires every week at at.

type Schedule

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

Schedule is an immutable, validated-on-use scheduling definition. A zero Schedule is invalid. Its location defaults to UTC unless In is called.

Typed-rule DST semantics are deliberately instant-based: Next walks real UTC minutes and matches their local civil representation. Spring-forward civil times do not run because they do not exist; a matching repeated fall-back time runs once for each real instant. Raw rules use robfig/cron's semantics.

func ScheduleFromDefinition

func ScheduleFromDefinition(definition ScheduleDefinition) (Schedule, error)

ScheduleFromDefinition reconstructs and validates a Schedule from its neutral domain projection. It rejects a mismatched Canonical value so an adapter cannot silently reinterpret persisted schedule fields.

func Spec

func Spec(rule Rule) Schedule

Spec wraps a rule in a Schedule. It does not panic: invalid declarations are retained and reported by Validate, Canonical, or Next.

func (Schedule) Anchor

func (schedule Schedule) Anchor(anchor time.Time) Schedule

Anchor returns a copy with a durable interval anchor. It applies only to Every rules and lets a runtime retain cadence across restarts. The anchor is normalized to PostgreSQL's microsecond timestamp precision.

func (Schedule) Canonical

func (schedule Schedule) Canonical() (string, error)

Canonical returns a normalized five-field cron expression, or @every for an interval. The separate location and anchor are intentionally not encoded in that expression.

func (Schedule) Definition

func (schedule Schedule) Definition() (ScheduleDefinition, error)

Definition returns a structured, normalized, persistence-ready projection.

func (Schedule) In

func (schedule Schedule) In(location *time.Location) Schedule

In returns a copy scheduled in location. A nil location is reported by Validate instead of panicking.

func (Schedule) IntervalAnchor

func (schedule Schedule) IntervalAnchor() (time.Time, bool)

IntervalAnchor reports the explicit durable anchor, if any.

func (Schedule) Location

func (schedule Schedule) Location() *time.Location

Location returns the schedule location after validation. UTC is the default.

func (Schedule) Next

func (schedule Schedule) Next(after time.Time) (time.Time, error)

Next returns the first matching real instant strictly after after. Raw rules delegate five-field parsing and occurrence evaluation to robfig/cron. Typed calendar rules are bounded to five years so malformed state cannot spin.

func (Schedule) String

func (schedule Schedule) String() string

String is the human-readable representation for logs and status pages.

func (Schedule) Validate

func (schedule Schedule) Validate() error

Validate verifies and compiles all schedule input without panics.

type ScheduleDefinition

type ScheduleDefinition struct {
	Kind      ScheduleKind `json:"kind"`
	Timezone  string       `json:"timezone"`
	Canonical string       `json:"canonical"`

	Hour     int          `json:"hour,omitempty"`
	Minute   int          `json:"minute,omitempty"`
	Weekday  time.Weekday `json:"weekday,omitempty"`
	MonthDay int          `json:"month_day,omitempty"`

	Interval  time.Duration `json:"interval,omitempty"`
	Anchor    time.Time     `json:"anchor,omitempty"`
	HasAnchor bool          `json:"has_anchor,omitempty"`
}

ScheduleDefinition is the neutral persistence and boundary projection of a Schedule. It deliberately contains ordinary typed Go values: adapters map this into relational SQLC parameters or API messages at their own boundary. Canonical is a normalized expression, while the typed fields preserve the pleasant DSL shape for schedule kinds that have one.

type ScheduleKind

type ScheduleKind string

ScheduleKind identifies the structured scheduling form in a Definition. It is a domain value, deliberately independent of protobuf and SQLC types.

const (
	ScheduleKindDaily          ScheduleKind = "daily"
	ScheduleKindWeekly         ScheduleKind = "weekly"
	ScheduleKindMonthly        ScheduleKind = "monthly"
	ScheduleKindLastDayOfMonth ScheduleKind = "last_day_of_month"
	ScheduleKindEvery          ScheduleKind = "every"
	ScheduleKindRaw            ScheduleKind = "raw"
)

type StoreSnapshot

type StoreSnapshot struct {
	Triggers    []TriggerState     `json:"triggers"`
	Occurrences []OccurrenceRecord `json:"occurrences"`
}

StoreSnapshot is a point-in-time copy of the active triggers and the most recent occurrences a store holds.

type TimeOfDay

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

TimeOfDay is a 24-hour local civil time.

func At24

func At24(hour int, minute ...int) TimeOfDay

At24 declares a 24-hour local civil time.

func Midnight

func Midnight() TimeOfDay

func Noon

func Noon() TimeOfDay

func (TimeOfDay) String

func (value TimeOfDay) String() string

type TriggerDefinition added in v0.2.0

type TriggerDefinition struct {
	Name     string             `json:"name"`
	Schedule ScheduleDefinition `json:"schedule"`
	CatchUp  CatchUpPolicy      `json:"catch_up"`
	Overlap  OverlapPolicy      `json:"overlap"`
}

TriggerDefinition is the static, persistence-neutral declaration of one trigger: its name, its schedule and its two policies. The operation a trigger invokes is registered in the process by name and never persisted; a store maps the definition to its own rows at its boundary.

func PreserveIntervalAnchor added in v0.2.0

func PreserveIntervalAnchor(incoming, existing TriggerDefinition) TriggerDefinition

PreserveIntervalAnchor keeps an established interval anchor when a trigger is declared again without one and nothing about its interval changed, so a restart does not restart the cadence.

type TriggerState added in v0.2.0

type TriggerState struct {
	Definition TriggerDefinition `json:"definition"`
	NextRunAt  time.Time         `json:"next_run_at"`
	CreatedAt  time.Time         `json:"created_at"`
	UpdatedAt  time.Time         `json:"updated_at"`
}

TriggerState is the durable scheduling cursor of one active trigger.

Directories

Path Synopsis
Package contract maps the cron domain model to validated Liquid Proto messages at HTTP and messaging boundaries.
Package contract maps the cron domain model to validated Liquid Proto messages at HTTP and messaging boundaries.

Jump to

Keyboard shortcuts

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