XINCGer/Unity3DTraining · error · InvalidProtocolBufferException

Invalid Duration value

Error message

Invalid Duration value: {token.StringValue}

What it means

After the token is confirmed a string, MergeDuration applies DurationRegex; strings that do not match the canonical Duration JSON grammar (sign, integer seconds, optional fractional part, mandatory 's' suffix) throw 'Invalid Duration value: ...'.

Solutions

  1. Format the duration as seconds with optional fraction and trailing 's': "5s", "-1.5s", "0.000000001s".
  2. Convert TimeSpan with $"{(long)ts.TotalSeconds}.{ts.Milliseconds...}s" or similar canonical form.
  3. Catch InvalidProtocolBufferException and normalize/repair the string before retrying.
  4. Validate with regex ^-?\d+(\.\d{1,9})?s$ before calling the parser.

Example fix

// before
var d = parser.Parse<Duration>("\"PT5S\"");
// after
var d = parser.Parse<Duration>("\"5s\"");
Defensive patterns

Strategy: validation

Validate before calling

static readonly System.Text.RegularExpressions.Regex DurationRegex =
    new(@"^-?\d+(\.\d{1,9})?s$");
bool IsCanonicalDuration(string s) => DurationRegex.IsMatch(s);

Type guard

bool IsValidDurationString(string s) => DurationRegex.IsMatch(s);

Try / catch

try { var d = JsonParser.Default.Parse<Duration>(jsonValue); }
catch (InvalidProtocolBufferException ex) when (ex.Message.StartsWith("Invalid Duration"))
{ log.Warn($"Bad duration '{raw}'"); throw new ArgumentException("duration must look like \"3.5s\"", ex); }

Prevention

When it happens

Trigger: Parsing values like "5" (no s), "5 sec", "PT5S" (ISO 8601), "1h30m", "", or ".5s" via JsonParser for a Duration field — anything not matching ^-?[0-9]+(\.[0-9]{1,9})?s$.

Common situations: Using ISO 8601 durations (PT5S) from other systems; omitting the trailing 's'; passing TimeSpan.ToString() output that lacks the 's'; locale-formatted numbers with commas.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


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

Appendix: source

Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonParser.cs:868

                message.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.SetValue(message, timestamp.Seconds);
                message.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.SetValue(message, timestamp.Nanos);
            }
            catch (FormatException)
            {
                throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
            }
        }

        private static void MergeDuration(IMessage message, JsonToken token)
        {
            if (token.Type != JsonToken.TokenType.StringValue)
            {
                throw new InvalidProtocolBufferException("Expected string value for Duration");
            }
            var match = DurationRegex.Match(token.StringValue);
            if (!match.Success)
            {
                throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
            }
            var sign = match.Groups["sign"].Value;
            var secondsText = match.Groups["int"].Value;
            // Prohibit leading insignficant zeroes
            if (secondsText[0] == '0' && secondsText.Length > 1)
            {
                throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
            }
            var subseconds = match.Groups["subseconds"].Value;
            var multiplier = sign == "-" ? -1 : 1;

            try
            {
                long seconds = long.Parse(secondsText, CultureInfo.InvariantCulture) * multiplier;
                int nanos = 0;
                if (subseconds != "")
                {
                    // This should always work, as we've got 1-9 digits.

View on GitHub (pinned to 016f98412e)