go-kratos/kratos · error
%s: seconds out of range %v
Error message
%s: seconds out of range %v
What it means
Form-encoding error while marshaling a google.protobuf.Timestamp to RFC 3339 string: the seconds component is outside the range this codec enforces before formatting (minTimestampSeconds..maxTimestampSeconds = 253402300799, i.e. year 9999). The check mirrors protojson's valid-timestamp window so that time.Unix formatting cannot overflow the RFC 3339 representation. The %s is google.protobuf.Timestamp and %v the offending seconds value.
Source
Thrown at encoding/form/well_known_types.go:48
// google.protobuf.Struct.
structMessageFullname protoreflect.FullName = "google.protobuf.Struct"
structFieldsFieldNumber protoreflect.FieldNumber = 1
fieldMaskFullName protoreflect.FullName = "google.protobuf.FieldMask"
)
func marshalTimestamp(m protoreflect.Message) (string, error) {
fds := m.Descriptor().Fields()
fdSeconds := fds.ByNumber(timestampSecondsFieldNumber)
fdNanos := fds.ByNumber(timestampNanosFieldNumber)
secsVal := m.Get(fdSeconds)
nanosVal := m.Get(fdNanos)
secs := secsVal.Int()
nanos := nanosVal.Int()
if secs < minTimestampSeconds || secs > maxTimestampSeconds {
return "", fmt.Errorf("%s: seconds out of range %v", timestampMessageFullname, secs)
}
if nanos < 0 || nanos > secondsInNanos {
return "", fmt.Errorf("%s: nanos out of range %v", timestampMessageFullname, nanos)
}
// Uses RFC 3339, where generated output will be Z-normalized and uses 0, 3,
// 6 or 9 fractional digits.
t := time.Unix(secs, nanos).Local()
x := t.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", nil
}
func marshalDuration(m protoreflect.Message) (string, error) {
fds := m.Descriptor().Fields()
fdSeconds := fds.ByNumber(durationSecondsFieldNumber)
fdNanos := fds.ByNumber(durationNanosFieldNumber)View on GitHub (pinned to 668db92c2c)
Solutions
- Build Timestamps only via timestamppb.New(time.Time) so seconds/nanos are always consistent and in range
- Fix unit bugs: verify the value you assign to seconds is actually seconds (divide millis/nanos before assigning)
- Replace sentinel extremes (MaxInt64) with a nil/zero Timestamp or an optional wrapper to mean 'unset'
- Clamp out-of-range values at the boundary (reject or cap to the 0001-9999 window) before encoding
Example fix
// before: ms stored as seconds -> seconds out of range
ts := ×tamppb.Timestamp{Seconds: time.Now().UnixNano() / 1e6}
// after: use the constructor
ts := timestamppb.New(time.Now()) Defensive patterns
Strategy: validation
Validate before calling
// Validate a Timestamp before encoding
func validTimestamp(ts *timestamppb.Timestamp) bool {
if ts == nil {
return true
}
return ts.AsTime().Year() >= 1 && ts.AsTime().Year() <= 9999
} Type guard
func validTimestampSeconds(secs int64) bool {
return secs >= minTimestampSeconds && secs <= maxTimestampSeconds // 9999-12-31T23:59:59
} Try / catch
if _, err := form.EncodeField(fd, val); err != nil {
if strings.Contains(err.Error(), "seconds out of range") {
// data bug: log the source record and skip/clamp, do not abort the whole response
log.Error("bad timestamp in payload", "field", fd.Name())
continue
}
} Prevention
- Construct timestamps only with timestamppb.New(time.Time)
- Never reuse math.MaxInt64 or -1 sentinels in Timestamp fields
- Double-check unit conversions (ms/us/ns) wherever raw seconds are computed
- Validate inbound timestamps at API boundaries before storing
When it happens
Trigger: Encoding a Timestamp field via form/EncodeField where the message carries seconds beyond 253402300799 (after 9999-12-31) or below the minimum (before 0001-01-01): zero-struct Timestamps default to 0 (fine), but timestamps built from arithmetic overflow, int64 max, or uninitialized garbage (e.g. treating nanoseconds as seconds) trip it. Also timestamps parsed from strings like '99999-01-01'.
Common situations: Unit bugs: storing nanos/millis in the seconds field; sentinel values like math.MaxInt64 or -1 used as 'not set' in a Timestamp; copying a Duration's seconds into a Timestamp; legacy data with year > 9999 fed through timestamppb.New.
Related errors
- %s: nanos out of range %v
- invalid path: %q is not a message
- field already set for oneof %q
- too many values for field %q: %s
- parsing field %q: %w
AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16).
Data as JSON: /api/errors/5e27dda31618a115.
Report an issue: GitHub.