thanos-io/thanos · error

cannot parse to a valid timestamp

Error message

cannot parse %q to a valid timestamp

What it means

The terminal error of parseTime: when the string is neither a parseable unix float nor a valid RFC3339Nano timestamp, this error is returned (and often wrapped by 'Invalid time value for %s' upstream). It is an ErrorBadData (HTTP 400) indicating the timestamp format is unrecognized.

Solutions

  1. Format the value as RFC3339Nano (e.g. 2024-01-02T15:04:05.999999999Z07:00).
  2. Or send a unix seconds float; convert ms epochs by dividing by 1000.
  3. URL-encode the timestamp so '+' and ':' survive transport.
  4. Add client-side pre-validation with the same two parse rules before calling the API.

Example fix

// before
params.set('start', '2024-01-02') // date-only, unparseable
// after
params.set('start', new Date('2024-01-02').toISOString()) // RFC3339
Defensive patterns

Strategy: validation

Validate before calling

const isValidApiTime = (s) => /^-?\d+(\.\d+)?$/.test(s) || !Number.isNaN(Date.parse(s));
// pre-check: if (!isValidApiTime(v)) fixBeforeRequest(v);

Type guard

function isParseableTimestamp(s) { return /^-?\d+(\.\d+)?$/.test(s) || !Number.isNaN(Date.parse(s)); }

Prevention

When it happens

Trigger: Any time parameter receiving a string that fails strconv.ParseFloat and time.Parse(RFC3339Nano), e.g. 'time=2024-01-02' (date only, no time/zone) or 'time=1704153600000' passed as float is fine, but 'time=xyz' fails both paths.

Common situations: Date-only strings without T/Z suffix, epoch milliseconds as an integer string (parses as float seconds — yields a far-future time rather than this error, a subtle bug), locale-formatted datetimes, or empty-ish garbage values that slipped past empty checks.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/9e2ff5364cfb9bed. Report an issue: GitHub.

Appendix: source

Thrown at pkg/api/query/v1.go:1599

		return defaultValue, nil
	}
	result, err := parseTime(val)
	if err != nil {
		return time.Time{}, errors.Wrapf(err, "Invalid time value for '%s'", paramName)
	}
	return result, nil
}

func parseTime(s string) (time.Time, error) {
	if t, err := strconv.ParseFloat(s, 64); err == nil {
		s, ns := math.Modf(t)
		ns = math.Round(ns*1000) / 1000
		return time.Unix(int64(s), int64(ns*float64(time.Second))), nil
	}
	if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
		return t, nil
	}
	return time.Time{}, errors.Errorf("cannot parse %q to a valid timestamp", s)
}

func parseDuration(s string) (time.Duration, error) {
	if d, err := strconv.ParseFloat(s, 64); err == nil {
		ts := d * float64(time.Second)
		if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) {
			return 0, errors.Errorf("cannot parse %q to a valid duration. It overflows int64", s)
		}
		return time.Duration(ts), nil
	}
	if d, err := model.ParseDuration(s); err == nil {
		return time.Duration(d), nil
	}
	return 0, errors.Errorf("cannot parse %q to a valid duration", s)
}

// parseLimitParam returning 0 means no limit is to be applied.
func parseLimitParam(s string) (int, error) {

View on GitHub (pinned to 35b8b99117)