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

  1. Build Timestamps only via timestamppb.New(time.Time) so seconds/nanos are always consistent and in range
  2. Fix unit bugs: verify the value you assign to seconds is actually seconds (divide millis/nanos before assigning)
  3. Replace sentinel extremes (MaxInt64) with a nil/zero Timestamp or an optional wrapper to mean 'unset'
  4. 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 := &timestamppb.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

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


AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16). Data as JSON: /api/errors/5e27dda31618a115. Report an issue: GitHub.