func MustParse
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.
Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.
Package cron parses the classic five-field cron expression and answers the only question a schedule exists to answer:...
gno.land/p/moul/x/daily/cron/v0The 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:
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.Next converts whatever it is handed.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.
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:
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.
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.
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.
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}Schedule is a parsed cron expression.
Expr returns the expression this schedule was parsed from, unmodified. A macro is reported as the macro, not as its expansion.
Matches reports whether the schedule fires during the minute containing t. Seconds and smaller are ignored: cron has no sub-minute resolution.
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.
NextN returns the next n firing times, in order. It returns fewer, possibly none, when the schedule runs out before n are found.