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.

README.md

3.58 Kb · 68 lines

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.