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

  1. 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
  2. Ensure any numeric offset includes a colon ('+05:00', not '+0500')
  3. Include full date, time and offset — a bare date or naive local time is rejected
  4. 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

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


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)