temporalio/temporal · warning

second value out of range: %v

Error message

second value out of range: %v

What it means

unmarshalTimestamp parses RFC 3339 timestamps used in Nexus HTTP headers and defensively bounds the parsed time to minTimestampSeconds..maxTimestampSeconds. If the resulting Unix seconds fall outside that range (e.g. year far in the past or future), it rejects the value with this error to prevent overflow downstream (e.g. when converting to duration or milliseconds).

Source

Thrown at common/nexus/nexusrpc/timestamp.go:36

	x := t.UTC().Format("2006-01-02T15:04:05.000000000")
	x = strings.TrimSuffix(x, "000")
	x = strings.TrimSuffix(x, "000")
	x = strings.TrimSuffix(x, ".000")
	return x + "Z"
}

// unmarshalTimestamp unmarshals a string into a Time instance. Uses RFC 3339, with some extra validation to ensure that
// seconds and subseconds are with an expected range.
// Copied from https://github.com/protocolbuffers/protobuf-go/blob/0b2c87d84c27802dae7248480444e22421ba577d/encoding/protojson/well_known_types.go#L749C1-L826C2
func unmarshalTimestamp(s string) (time.Time, error) {
	t, err := time.Parse(time.RFC3339Nano, s)
	if err != nil {
		return t, err
	}
	// Validate seconds.
	secs := t.Unix()
	if secs < minTimestampSeconds || secs > maxTimestampSeconds {
		return t, fmt.Errorf("second value out of range: %v", secs)
	}
	// Validate subseconds.
	i := strings.LastIndexByte(s, '.')  // start of subsecond field
	j := strings.LastIndexAny(s, "Z-+") // start of timezone field
	if i >= 0 && j >= i && j-i > len(".999999999") {
		return t, fmt.Errorf("invalid subsecond value %v", s)
	}
	return t, nil
}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Fix the producer to emit current-time RFC 3339 timestamps (time.Now().UTC().Format(time.RFC3339Nano)).
  2. Check for zero-value time.Time being formatted accidentally and guard against it.
  3. If a legitimate use needs a wider range, adjust min/maxTimestampSeconds consciously.
  4. Reject/log the request at the edge with a 400 before deeper processing.

Example fix

// before
header.Set("request-time", t.Format(time.RFC3339Nano)) // t is zero value -> year 1 -> out of range
// after
if t.IsZero() {
    t = time.Now().UTC()
}
header.Set("request-time", t.Format(time.RFC3339Nano))
Defensive patterns

Strategy: validation

Validate before calling

secs := t.Unix()
if t.IsZero() || secs < minTimestampSeconds || secs > maxTimestampSeconds {
    return errors.New("timestamp out of supported range")
}

Type guard

func validTimestamp(t time.Time) bool {
    return !t.IsZero() && t.Year() >= 1 && t.Year() <= 9999 && t.Unix() >= minTimestampSeconds && t.Unix() <= maxTimestampSeconds
}

Try / catch

t, err := time.Parse(time.RFC3339Nano, raw)
if err != nil || t.IsZero() {
    return fmt.Errorf("invalid request-time header: %w", err)
}

Prevention

When it happens

Trigger: A client sends a timestamp header with an extreme value — e.g. year 0001 or year 9999, or a bogus huge year — which parses as valid RFC 3339 but whose Unix seconds exceed the bounds, from ServeHTTP.

Common situations: Buggy clients constructing timestamps from zero-valued time.Time (year 1); clock misconfiguration; hand-crafted or fuzzed HTTP requests with adversarial headers.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/df8d02f0ec4604b2. Report an issue: GitHub.