XINCGer/Unity3DTraining · error · InvalidProtocolBufferException
Invalid Timestamp value
Error message
Invalid Timestamp value: {token.StringValue} What it means
After confirming the token is a string, MergeTimestamp matches it against TimestampRegex, which enforces the RFC 3339 shape (calendar date, 'T' or 't' separator, time, optional fractional seconds, and 'Z'/offset). Strings that are close but not conformant — missing timezone, wrong separator, unparseable offsets — fail the regex and throw InvalidProtocolBufferException('Invalid Timestamp value: ' + value).
Solutions
- Emit RFC 3339 UTC strings ending in 'Z', e.g. dateTime.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'") or the 'o' round-trip format on a UTC DateTime
- Ensure any numeric offset includes a colon ('+05:00', not '+0500')
- Include full date, time and offset — a bare date or naive local time is rejected
- Normalize/pre-parse with DateTimeOffset.Parse + .ToString("o") before embedding into JSON
Example fix
// before
string ts = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); // 2024-01-15 10:30:00, no T/Z
// after
string ts = DateTime.UtcNow.ToString("o"); // 2024-01-15T10:30:00.0000000Z Defensive patterns
Strategy: validation
Validate before calling
static readonly System.Text.RegularExpressions.Regex Rfc3339 =
new System.Text.RegularExpressions.Regex(@"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$");
bool IsValidTimestampString(string s) => Rfc3339.IsMatch(s); Try / catch
try { return JsonParser.Default.Parse<T>(json); }
catch (InvalidProtocolBufferException ex) when (ex.Message.StartsWith("Invalid Timestamp value")) {
logger.LogWarning(ex, "Timestamp string does not match RFC 3339");
return null;
} Prevention
- Always emit UTC timestamps ending in 'Z' (DateTime.UtcNow.ToString("o"))
- Include colons in numeric offsets ('+05:00', not '+0500')
- Reject date-only or naive local times at your API boundary
- Unit-test timestamp serialization against the proto JSON conformance patterns
When it happens
Trigger: JsonParser.Parse<T> with Timestamp fields given strings like '2024-01-15 10:30:00' (space instead of T with wrong format), '2024-01-15' (date only), '2024-01-15T10:30:00' (no offset/Z), or invalid offsets like '2024-01-15T10:30:00+0500'.
Common situations: DateTime.ToString() defaults that omit 'Z' or use space separators; SQL datetime strings pasted into JSON; offsets formatted without the required colon; date-only values used where full timestamps are required.
Related errors
- Expected string value for Timestamp
- Invalid field type
- Unable to format value of type
- Type registry has no descriptor for type name '
- Struct fields cannot have an empty key or a null value.
AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12).
Data as JSON: /api/errors/6b377985bd561210.
Report an issue: GitHub.
Appendix: source
Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonParser.cs:792
{
if ((isPositiveInfinity && text != "Infinity") ||
(isNegativeInfinity && text != "-Infinity") ||
(isNaN && text != "NaN"))
{
throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
}
}
private static void MergeTimestamp(IMessage message, JsonToken token)
{
if (token.Type != JsonToken.TokenType.StringValue)
{
throw new InvalidProtocolBufferException("Expected string value for Timestamp");
}
var match = TimestampRegex.Match(token.StringValue);
if (!match.Success)
{
throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
}
var dateTime = match.Groups["datetime"].Value;
var subseconds = match.Groups["subseconds"].Value;
var offset = match.Groups["offset"].Value;
try
{
DateTime parsed = DateTime.ParseExact(
dateTime,
"yyyy-MM-dd'T'HH:mm:ss",
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
// TODO: It would be nice not to have to create all these objects... easy to optimize later though.
Timestamp timestamp = Timestamp.FromDateTime(parsed);
int nanosToAdd = 0;
if (subseconds != "")
{
// This should always work, as we've got 1-9 digits.View on GitHub (pinned to 016f98412e)