robfig/cron · error

failed to parse duration

Error message

failed to parse duration %s: %s

What it means

Thrown by parseDescriptor in parser.go when a descriptor starting with the '@every ' prefix has a suffix that time.ParseDuration cannot parse. The remainder after '@every ' must be a valid Go duration string such as '1h30m' or '15m'. The underlying ParseDuration error is embedded in the message.

Solutions

  1. Use a valid Go duration string: units are ns, us, ms, s, m, h (e.g. '@every 1h30m')
  2. Convert days/weeks yourself: '@every 24h' for daily, '@every 168h' for weekly
  3. Validate the duration from config with time.ParseDuration before embedding it in the descriptor
  4. If you need '1d' style syntax, write a wrapper that translates human durations to Go durations before calling Parse

Example fix

// before
cron.Parse("@every 1d") // 'd' is not a Go duration unit
// after
cron.Parse("@every 24h")
Defensive patterns

Strategy: validation

Validate before calling

if strings.HasPrefix(spec, "@every ") {
	if _, err := time.ParseDuration(strings.TrimPrefix(spec, "@every ")); err != nil {
		return fmt.Errorf("invalid @every duration: %w", err)
	}
}

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 '@every xyz', '@every 5', '@every 5min' (Go durations require 'm' for minutes, not 'min'... actually 'min' is invalid; must be 'm'), '@every 1d' (days are not a Go duration unit), or '@every' followed by an empty/whitespace suffix.

Common situations: Users writing human-style durations ('1d', '5min', '2w') that Go's duration syntax doesn't accept; config values with missing units ('@every 30'); localizing the string so the prefix matches but the number doesn't; Go version migrations are irrelevant here — the unit set is fixed by time.ParseDuration.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


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

Appendix: source

Thrown at parser.go:428

	case "@hourly":
		return &SpecSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     all(hours),
			Dom:      all(dom),
			Month:    all(months),
			Dow:      all(dow),
			Location: loc,
		}, nil

	}

	const every = "@every "
	if strings.HasPrefix(descriptor, every) {
		duration, err := time.ParseDuration(descriptor[len(every):])
		if err != nil {
			return nil, fmt.Errorf("failed to parse duration %s: %s", descriptor, err)
		}
		return Every(duration), nil
	}

	return nil, fmt.Errorf("unrecognized descriptor: %s", descriptor)
}

View on GitHub (pinned to bc59245fe1)