robfig/cron · error

negative number ( ) not allowed

Error message

negative number (%d) not allowed: %s

What it means

Thrown by mustParseInt in parser.go when a cron field token parses as a negative integer. Cron fields are non-negative by definition (seconds start at 0), so any '-N' token in a position expected to be a plain number is rejected. Note ranges must use '-', e.g. '5-10'; a bare negative like '-5' or '-5-10' hits this error.

Solutions

  1. Clamp or validate numeric inputs to >= 0 before formatting them into the spec
  2. Fix accidental concatenation like start + "-" + end when start/end are negative strings
  3. If a negative offset was intended, apply it in code (e.g. subtract duration before scheduling) instead of in the cron spec
  4. Validate spec-bearing config at load time with cron.Parse

Example fix

// before
spec := fmt.Sprintf("0 %d * * * *", offset) // offset can be -5
// after
if offset < 0 { return errors.New("offset must be >= 0") }
spec := fmt.Sprintf("0 %d * * * *", offset)
Defensive patterns

Strategy: validation

Validate before calling

func nonNegative(n int) bool { return n >= 0 }
// apply to every variable interpolated into a cron spec

Try / catch

sched, err := cron.Parse(spec)
if err != nil {
	return fmt.Errorf("invalid cron spec %q: %w", spec, err)
}

Prevention

When it happens

Trigger: Calling cron.Parse with tokens like '0 -5 * * * *', '0 * -10-20 * * *', or a spec built from a signed integer variable (e.g. a negative offset from config) interpolated into the string.

Common situations: Passing negative values from config or CLI arguments into spec construction; arithmetic on time values yielding negative offsets that are formatted into the schedule string; confusion between the range separator '-' and a negative sign when concatenating tokens.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of robfig/cron@bc59245fe1 (2026-09-07). Data as JSON: /api/errors/d4505e8ebf68d81e. Report an issue: GitHub.

Appendix: source

Thrown at parser.go:337

// parseIntOrName returns the (possibly-named) integer contained in expr.
func parseIntOrName(expr string, names map[string]uint) (uint, error) {
	if names != nil {
		if namedInt, ok := names[strings.ToLower(expr)]; ok {
			return namedInt, nil
		}
	}
	return mustParseInt(expr)
}

// mustParseInt parses the given expression as an int or returns an error.
func mustParseInt(expr string) (uint, error) {
	num, err := strconv.Atoi(expr)
	if err != nil {
		return 0, fmt.Errorf("failed to parse int from %s: %s", expr, err)
	}
	if num < 0 {
		return 0, fmt.Errorf("negative number (%d) not allowed: %s", num, expr)
	}

	return uint(num), nil
}

// getBits sets all bits in the range [min, max], modulo the given step size.
func getBits(min, max, step uint) uint64 {
	var bits uint64

	// If step is 1, use shifts.
	if step == 1 {
		return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
	}

	// Else, use a simple loop.
	for i := min; i <= max; i += step {
		bits |= 1 << i
	}

View on GitHub (pinned to bc59245fe1)