go-kratos/kratos · error
%s: nanos out of range %v
Error message
%s: nanos out of range %v
What it means
Form-encoding error while marshaling a google.protobuf.Timestamp: the nanoseconds component is negative or greater than secondsInNanos (999999999). Valid proto timestamps require 0 <= nanos <= 999999999; the codec enforces this before time.Unix formatting, mirroring protojson validation.
Source
Thrown at encoding/form/well_known_types.go:51
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)
secsVal := m.Get(fdSeconds)
nanosVal := m.Get(fdNanos)View on GitHub (pinned to 668db92c2c)
Solutions
- Always construct with timestamppb.New(time.Time) which normalizes seconds/nanos correctly
- When converting manually, normalize: carry nanos >= 1e9 into seconds and flip sign handling for negatives before setting fields
- Sanitize inbound timestamps at the trust boundary (validate 0 <= nanos <= 999999999, reject otherwise) before they reach form encoding
- For interop with runtimes emitting normalized-negative pairs, recombine into a single seconds value (sec = s; if s < 0 && n > 0 { sec++; n -= 1e9 })
Example fix
// before: un-normalized hand-built timestamp
ts := ×tamppb.Timestamp{Seconds: 10, Nanos: 1500000000}
// after: constructor normalizes automatically
ts := timestamppb.New(time.Unix(11, 500000000)) Defensive patterns
Strategy: validation
Validate before calling
// Validate nanos range before encoding
func validTimestampNanos(nanos int32) bool {
return nanos >= 0 && nanos <= 999999999
} Type guard
func validTimestampMsg(ts *timestamppb.Timestamp) bool {
return ts == nil || (validTimestampSeconds(ts.Seconds) && validTimestampNanos(ts.Nanos))
} Try / catch
if _, err := form.EncodeField(fd, val); err != nil {
if strings.Contains(err.Error(), "nanos out of range") {
ts := val.Message().Interface().(*timestamppb.Timestamp)
normalized := timestamppb.New(ts.AsTime()) // re-derive from a time.Time
_ = normalized
}
} Prevention
- Avoid hand-building Timestamp{Seconds,Nanos}; use timestamppb.New
- Normalize interop formats that encode negative fractions as {sec<0, nanos>0}
- Validate 0<=nanos<=999999999 wherever timestamps enter the system
- Property-test timestamp round-trips (form encode/decode) with random times
When it happens
Trigger: Encoding a Timestamp constructed by hand with invalid nanos: passing a negative fraction, nanos borrowed across a second boundary incorrectly (e.g. {Seconds:-1, Nanos:500000000} style normalized-negative representations from other runtimes), nanos holding a full second or more (1000000000+), or a millisecond value mistakenly written into nanos scaled wrong (e.g. 1e6*ms overflow patterns).
Common situations: Interoperating with C++/Python protobuf code that normalizes negative durations into {negative seconds, positive nanos} and that pattern leaking into Timestamp; hand-rolled conversions in ETL jobs; copy-paste from Duration math where nanos can exceed a second before normalization.
Related errors
- %s: seconds 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/90a34b919b46866e.
Report an issue: GitHub.