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
- Fix the producer to emit current-time RFC 3339 timestamps (time.Now().UTC().Format(time.RFC3339Nano)).
- Check for zero-value time.Time being formatted accidentally and guard against it.
- If a legitimate use needs a wider range, adjust min/maxTimestampSeconds consciously.
- 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
- Always emit timestamps from time.Now().UTC().Format(time.RFC3339Nano)
- Guard against zero-value time.Time being formatted into headers
- Validate timestamp headers at the edge of your service before deep processing
- Never hand-concatenate timestamp strings in clients
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
- invalid subsecond value %v
- link type is empty
- link type contains invalid char (valid chars: alphanumeric,
- empty operation name
- empty operation token
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/df8d02f0ec4604b2.
Report an issue: GitHub.