// Package schedule wraps `pkg/cronutil` with the force-next-turn skip // rule used by Game Master's scheduler. // // The wrapper is pure: callers pass the current `skip_next_tick` flag // and the wrapper returns both the next firing time and a boolean that // reports whether the flag was consumed. The runtime-record store is // responsible for persisting the cleared flag; this package never // touches it. // // `gamemaster/README.md §Force-next-turn` describes the rule: // // If `skip_next_tick=true`, advance by one extra cron step and clear // the flag. package schedule import ( "time" "galaxy/cronutil" ) // Schedule wraps `cronutil.Schedule` with the GM-specific // skip-next-tick semantics. The zero value is not usable; callers // obtain a Schedule from Parse. type Schedule struct { inner cronutil.Schedule } // Parse parses expr as a five-field cron expression and returns the // resulting Schedule. Parse returns an error if expr is rejected by the // underlying cronutil parser. func Parse(expr string) (Schedule, error) { inner, err := cronutil.Parse(expr) if err != nil { return Schedule{}, err } return Schedule{inner: inner}, nil } // Next returns the next firing time strictly after `after`, honouring // the skip flag. // // When `skip` is false, Next returns `cronutil.Schedule.Next(after)` // and reports `skipConsumed=false`. // // When `skip` is true, Next computes the cron step immediately after // `after`, then advances by one further cron step and returns that // time with `skipConsumed=true`. The caller is responsible for // persisting the cleared flag after observing `skipConsumed`. // // All returned times are in UTC; cronutil.Schedule already enforces // UTC normalisation on its inputs and outputs. func (s Schedule) Next(after time.Time, skip bool) (time.Time, bool) { first := s.inner.Next(after) if !skip { return first, false } return s.inner.Next(first), true }