{"record":{"id":"90a34b919b46866e","repo":"go-kratos/kratos","slug":"s-nanos-out-of-range-v","errorCode":null,"errorMessage":"%s: nanos out of range %v","messagePattern":"(.+?): nanos out of range (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"encoding/form/well_known_types.go","lineNumber":51,"sourceCode":"\tstructFieldsFieldNumber protoreflect.FieldNumber = 1\n\n\tfieldMaskFullName protoreflect.FullName = \"google.protobuf.FieldMask\"\n)\n\nfunc marshalTimestamp(m protoreflect.Message) (string, error) {\n\tfds := m.Descriptor().Fields()\n\tfdSeconds := fds.ByNumber(timestampSecondsFieldNumber)\n\tfdNanos := fds.ByNumber(timestampNanosFieldNumber)\n\n\tsecsVal := m.Get(fdSeconds)\n\tnanosVal := m.Get(fdNanos)\n\tsecs := secsVal.Int()\n\tnanos := nanosVal.Int()\n\tif secs < minTimestampSeconds || secs > maxTimestampSeconds {\n\t\treturn \"\", fmt.Errorf(\"%s: seconds out of range %v\", timestampMessageFullname, secs)\n\t}\n\tif nanos < 0 || nanos > secondsInNanos {\n\t\treturn \"\", fmt.Errorf(\"%s: nanos out of range %v\", timestampMessageFullname, nanos)\n\t}\n\t// Uses RFC 3339, where generated output will be Z-normalized and uses 0, 3,\n\t// 6 or 9 fractional digits.\n\tt := time.Unix(secs, nanos).Local()\n\tx := t.Format(\"2006-01-02T15:04:05.000000000\")\n\tx = strings.TrimSuffix(x, \"000\")\n\tx = strings.TrimSuffix(x, \"000\")\n\tx = strings.TrimSuffix(x, \".000\")\n\treturn x + \"Z\", nil\n}\n\nfunc marshalDuration(m protoreflect.Message) (string, error) {\n\tfds := m.Descriptor().Fields()\n\tfdSeconds := fds.ByNumber(durationSecondsFieldNumber)\n\tfdNanos := fds.ByNumber(durationNanosFieldNumber)\n\n\tsecsVal := m.Get(fdSeconds)\n\tnanosVal := m.Get(fdNanos)","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/go-kratos/kratos/blob/668db92c2c001e9552594ba5a8aede8456af6d7e/encoding/form/well_known_types.go#L33-L69","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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 })"],"exampleFix":"// before: un-normalized hand-built timestamp\nts := &timestamppb.Timestamp{Seconds: 10, Nanos: 1500000000}\n\n// after: constructor normalizes automatically\nts := timestamppb.New(time.Unix(11, 500000000))","handlingStrategy":"validation","validationCode":"// Validate nanos range before encoding\nfunc validTimestampNanos(nanos int32) bool {\n\treturn nanos >= 0 && nanos <= 999999999\n}","typeGuard":"func validTimestampMsg(ts *timestamppb.Timestamp) bool {\n\treturn ts == nil || (validTimestampSeconds(ts.Seconds) && validTimestampNanos(ts.Nanos))\n}","tryCatchPattern":"if _, err := form.EncodeField(fd, val); err != nil {\n\tif strings.Contains(err.Error(), \"nanos out of range\") {\n\t\tts := val.Message().Interface().(*timestamppb.Timestamp)\n\t\tnormalized := timestamppb.New(ts.AsTime()) // re-derive from a time.Time\n\t\t_ = normalized\n\t}\n}","preventionTips":["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"],"tags":["go","kratos","form-binding","protobuf","timestamp","validation"],"backgroundTag":null,"analyzedSha":"668db92c2c001e9552594ba5a8aede8456af6d7e","analyzedAt":"2026-08-16T02:07:20.704Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}