// Package cronutil provides a thin wrapper over github.com/robfig/cron/v3 // for parsing the five-field cron expressions used by Galaxy services to // describe periodic schedules such as turn_schedule. It exposes only the // parser and a Next computation; no scheduler runtime, no logging, and no // concurrency primitives. package cronutil import ( "fmt" "time" "github.com/robfig/cron/v3" ) // fiveFieldParser parses standard five-field cron expressions // (minute, hour, day-of-month, month, day-of-week). The grammar matches // what Galaxy services accept for turn_schedule and is the only grammar // supported by this package; six-field expressions with a seconds field // are rejected. var fiveFieldParser = cron.NewParser( cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow, ) // Schedule holds a parsed five-field cron expression and computes the // next firing time after a given moment. The zero value is not usable; // callers obtain a Schedule from Parse. type Schedule struct { inner cron.Schedule } // Parse parses expr as a five-field cron expression and returns the // resulting Schedule. Parse returns an error if expr is empty, contains // a seconds field, or is otherwise rejected by the underlying parser. func Parse(expr string) (Schedule, error) { inner, err := fiveFieldParser.Parse(expr) if err != nil { return Schedule{}, fmt.Errorf("cronutil: parse %q: %w", expr, err) } return Schedule{inner: inner}, nil } // Next returns the next firing time strictly after after. The returned // time is always in UTC; callers passing UTC values therefore get UTC // values back. Calling Next on a zero-value Schedule panics. func (s Schedule) Next(after time.Time) time.Time { return s.inner.Next(after.UTC()).UTC() }