XINCGer/Unity3DTraining · error · InvalidOperationException

Non-normalized timestamp value

Error message

Non-normalized timestamp value

What it means

Timestamp.ToJson (reached via ToDiagnosticString) serializes RFC 3339 timestamps, which requires normalized values: Nanos in [0, 999999999] and Seconds within the representable range (0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z). Out-of-range Seconds get a '@warning' JSON fallback, but non-normalized values (e.g. negative Nanos) throw InvalidOperationException.

Solutions

  1. Normalize before serializing: move nanos overflow into Seconds and ensure 0 <= Nanos < 1_000_000_000
  2. Construct via Timestamp.FromDateTime(DateTime.UtcNow) or from the parser rather than setting fields directly
  3. Validate Nanos/Seconds before calling ToJson and repair or reject invalid values
  4. Log the raw Seconds/Nanos from the exception path to identify the faulty producer

Example fix

// before
var ts = new Timestamp { Seconds = 10, Nanos = -1 };
var s = ts.ToDiagnosticString(); // throws
// after
var ts2 = new Timestamp { Seconds = 9, Nanos = 999_999_999 };
var s2 = ts2.ToDiagnosticString(); // "2009-..." normalized RFC3339
Defensive patterns

Strategy: validation

Validate before calling

static bool IsNormalizedTimestamp(Timestamp t) => t.Nanos >= 0 && t.Nanos < 1_000_000_000 && t.Seconds >= -62135596800L && t.Seconds <= 253402300799L;

Type guard

static bool IsSerializableTimestamp(Timestamp t) => t != null && t.Nanos >= 0 && t.Nanos < 1_000_000_000;

Try / catch

try { return ts.ToDiagnosticString(); } catch (InvalidOperationException ex) { log.Warn(ex, "Non-normalized timestamp {Seconds}/{Nanos}", ts.Seconds, ts.Nanos); return $"{ts.Seconds}.{ts.Nanos} (non-normalized)"; }

Prevention

When it happens

Trigger: Calling ToDiagnosticString()/ToJson() on a Timestamp with Nanos < 0, Nanos >= 1_000_000_000, or Seconds outside the RFC 3339 representable window.

Common situations: Timestamps built by manual arithmetic on Seconds/Nanos without carrying; interop with producers emitting negative nanos; overflow when converting milliseconds/microseconds to nanos; corrupted or fuzzed wire data.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12). Data as JSON: /api/errors/6bdc5997ca0b3c09. Report an issue: GitHub.

Appendix: source

Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/WellKnownTypes/TimestampPartial.cs:223

                // Use .NET's formatting for the value down to the second, including an opening double quote (as it's a string value)
                DateTime dateTime = UnixEpoch.AddSeconds(seconds);
                var builder = new StringBuilder();
                builder.Append('"');
                builder.Append(dateTime.ToString("yyyy'-'MM'-'dd'T'HH:mm:ss", CultureInfo.InvariantCulture));
                Duration.AppendNanoseconds(builder, nanoseconds);
                builder.Append("Z\"");
                return builder.ToString();
            }
            if (diagnosticOnly)
            {
                return string.Format(CultureInfo.InvariantCulture,
                    "{{ \"@warning\": \"Invalid Timestamp\", \"seconds\": \"{0}\", \"nanos\": {1} }}",
                    seconds,
                    nanoseconds);
            }
            else
            {
                throw new InvalidOperationException("Non-normalized timestamp value");
            }
        }

        /// <summary>
        /// Returns a string representation of this <see cref="Timestamp"/> for diagnostic purposes.
        /// </summary>
        /// <remarks>
        /// Normally the returned value will be a JSON string value (including leading and trailing quotes) but
        /// when the value is non-normalized or out of range, a JSON object representation will be returned
        /// instead, including a warning. This is to avoid exceptions being thrown when trying to
        /// diagnose problems - the regular JSON formatter will still throw an exception for non-normalized
        /// values.
        /// </remarks>
        /// <returns>A string representation of this value.</returns>
        public string ToDiagnosticString()
        {
            return ToJson(Seconds, Nanos, true);
        }

View on GitHub (pinned to 016f98412e)