Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

v0 source pure

Package cron parses the classic five-field cron expression and answers the only question a schedule exists to answer:...

Readme View source

gno.land/p/moul/x/daily/cron/v0

The five-field cron expression, parsed and answered: Parse, MustParse, Next, NextN, Matches, Expr.

1import "gno.land/p/moul/x/daily/cron/v0"
2
3s, err := cron.Parse("30 8 * * mon-fri")
4next, ok := s.Next(time.Now())   // the first weekday 08:30 after now
5s.Matches(t)                     // does it fire during the minute holding t
6s.NextN(t, 5)                    // the next five, in order

The grammar is the one every crontab uses, in the order minute, hour, day-of-month, month, day-of-week. Each field is a comma-separated list whose items are a value, a range, a star, or any of those with a step: 5, 5-30, *, */15, 5-30/5, 5/20. Months take jan..dec, weekdays sun..sat, and the seven @macros are accepted (@hourly, @daily, @weekly, @monthly, @yearly, @annually, @midnight).

A chain has no timer, so a schedule here is never a thing that runs. Nothing on gno.land wakes a realm up. A schedule is a thing that is checked: the realm stores the expression, and whoever calls in next asks whether the due instant has passed. That is the whole reason to want Next on chain rather than a cron daemon off it, and it is why this package is a parser and a calendar walk with no scheduler attached.

Four behaviours worth knowing before you use it:

  • When both day fields are narrowed, the match is their UNION. 0 0 13 * 5 is the 13th of the month or any Friday, never the intersection, which is why that expression also fires on ordinary Fridays. When only one of the two is narrowed the other matches everything, so union and intersection coincide and there is nothing to decide. This is the rule a reimplementation gets wrong, and the one the package is worth importing for.
  • Next returns (time.Time, bool), and the bool is not decoration. 0 0 30 2 * is a well formed expression with no instant behind it. The walk gives up after five years and says so, rather than scanning forever or returning a zero time a caller would use as a date.
  • Everything is UTC. A block timestamp carries no local zone, and a schedule whose answer depended on one would not be the same schedule on every validating node. Next converts whatever it is handed.
  • Resolution is the minute, and Next is exclusive. Seconds and smaller are discarded, so a schedule handed its own firing time answers with the following one rather than with itself. That is what makes t = s.Next(t) a loop and not a fixed point.

Parse validates the grammar and the ranges and nothing else. An expression that is legal but can never fire parses successfully, because only Next is in a position to discover that. Parse errors name the field they came from: five fields all accept numbers, so out of range on its own says nothing about which one to go and fix.

A Schedule is immutable once parsed and holds no state, so MustParse at package scope is the intended way to keep a constant schedule in a realm.

Live demo: r/moul/x/daily/crondemo.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.

Overview

Package cron parses the classic five-field cron expression and answers the only question a schedule exists to answer: when does it next fire.

The grammar is the one every crontab uses, in the order minute, hour, day-of-month, month, day-of-week:

Example
1*/15 9-17 * * mon-fri
2│    │    │ │ │
3│    │    │ │ └── day of week, 0-7 or sun..sat (0 and 7 are both Sunday)
4│    │    │ └──── month, 1-12 or jan..dec
5│    │    └────── day of month, 1-31
6│    └─────────── hour, 0-23
7└──────────────── minute, 0-59

Each field is a comma-separated list whose items are a value, a range, a star, or any of those followed by a step: 5, 5-30, *, */15, 5-30/5, 5/20. The seven @macros are accepted too (@hourly, @daily, @weekly, @monthly, @yearly, @annually, @midnight).

A Schedule is immutable once parsed and holds no state, so it is safe to build one at package scope and share it. Everything is computed in UTC: a block timestamp has no local zone, and a schedule whose answer depended on one would not be the same schedule on every validating node.

Functions 2

func MustParse

1func MustParse(expr string) *Schedule
source

MustParse is Parse for an expression written by the programmer rather than supplied by a caller. It panics instead of returning an error, so a constant schedule can be a package-level variable.

func Parse

1func Parse(expr string) (*Schedule, error)
source

Parse compiles a cron expression.

It validates the grammar and the ranges, and nothing else: an expression that is well formed but can never fire parses successfully, because only Next is in a position to discover that.

Types 1

type Schedule

struct
 1type Schedule struct {
 2	expr string
 3
 4	minute [60]bool
 5	hour   [24]bool
 6	dom    [32]bool // 1..31
 7	month  [13]bool // 1..12
 8	dow    [7]bool  // 0..6, Sunday is 0
 9
10	// Whether each day field was narrowed from "*". The pair decides how the
11	// two are combined; see matchDay.
12	domRestricted bool
13	dowRestricted bool
14}
source

Schedule is a parsed cron expression.

Methods on Schedule

func Expr

method on Schedule
1func (s *Schedule) Expr() string
source

Expr returns the expression this schedule was parsed from, unmodified. A macro is reported as the macro, not as its expansion.

func Matches

method on Schedule
1func (s *Schedule) Matches(t time.Time) bool
source

Matches reports whether the schedule fires during the minute containing t. Seconds and smaller are ignored: cron has no sub-minute resolution.

func Next

method on Schedule
1func (s *Schedule) Next(after time.Time) (time.Time, bool)
source

Next returns the first instant strictly after `after` at which the schedule fires, and whether there is one.

The second return value is not decoration. "0 0 30 2 *" is a legal expression with no instant behind it, and a caller that assumed a match would scan forever.

func NextN

method on Schedule
1func (s *Schedule) NextN(after time.Time, n int) []time.Time
source

NextN returns the next n firing times, in order. It returns fewer, possibly none, when the schedule runs out before n are found.

Dependencies

Imports 4

/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/cron/v0

Imported by indexer

Source Files 4