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
- Normalize before serializing: move nanos overflow into Seconds and ensure 0 <= Nanos < 1_000_000_000
- Construct via Timestamp.FromDateTime(DateTime.UtcNow) or from the parser rather than setting fields directly
- Validate Nanos/Seconds before calling ToJson and repair or reject invalid values
- 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
- Normalize Nanos into Seconds after any arithmetic
- Build timestamps via FromDateTime/FromDateTimeOffset, not raw fields
- Sanitize wire input before storing/serializing Timestamps
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
- Non-normalized duration value
- Invalid field mask to be converted to JSON
- Timestamp contains invalid values: Seconds=
- Conversion from DateTime to Timestamp requires the DateTime…
- Did not write as much data as expected.
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)