apache/beam · error · ValueError

Could not parse RFC 3339 string

Error message

Could not parse RFC 3339 string '{}' due to error: '{}'.

What it means

from_rfc3339() parses the string with dateutil.parser.isoparse(); if that raises ValueError, it is re-raised with a message including both the input string and the underlying parse error. This means the input was not a valid RFC 3339 / ISO-8601 timestamp.

Solutions

  1. Validate/fix the input to RFC 3339 form, e.g. '2024-01-01T00:00:00Z'
  2. Read the wrapped inner error (the trailing 'error' in the message) to pinpoint the malformed part
  3. Pre-parse with dateutil.parser.parse for lenient formats, then convert to an aware UTC datetime and use Timestamp.of

Example fix

// before
ts = Timestamp.from_rfc3339(user_input)  # '01/02/2024 3pm'
// after
dt = dateutil.parser.parse(user_input)
ts = Timestamp.of(dt.astimezone(pytz.utc))
Defensive patterns

Strategy: try-catch

Validate before calling

RFC3339_RE = re.compile(r'^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:?\d{2})$')
if not RFC3339_RE.match(s):
    raise ValueError(f'not RFC 3339: {s!r}')

Try / catch

try:
    ts = Timestamp.from_rfc3339(s)
except ValueError as e:
    log.error('bad timestamp %r: %s', s, e)
    ts = None  # or dead-letter the record

Prevention

When it happens

Trigger: Timestamp.from_rfc3339('2024-13-45T99:00:00Z'); 'Jan 1 2024'; empty string; truncated strings like '2024-01-01T10:'; timezone-less strings that isoparse rejects in strict contexts.

Common situations: Free-form user input into an API field; log-parsing pipelines fed non-RFC3339 formats; typos like double T or missing seconds parts; locale-formatted dates.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/75f7905565f98e77. Report an issue: GitHub.

Appendix: source

Thrown at sdks/python/apache_beam/utils/timestamp.py:191

  @classmethod
  def from_rfc3339(cls, rfc3339: str) -> 'Timestamp':
    """Create a ``Timestamp`` instance from an RFC 3339 compliant string.

    Fractional seconds up to microseconds produce a microsecond-precision
    Timestamp; a longer fraction (up to nanoseconds) produces a Timestamp
    whose precision matches the number of fractional digits.

    .. note::
      All timezones are implicitly converted to UTC.

    Args:
      rfc3339: String in RFC 3339 form.
    """
    try:
      dt = dateutil.parser.isoparse(rfc3339).astimezone(pytz.UTC)
    except ValueError as e:
      raise ValueError(
          "Could not parse RFC 3339 string '{}' due to error: '{}'.".format(
              rfc3339, e))
    timestamp = cls.from_utc_datetime(dt)
    # dateutil silently truncates fractional seconds to microseconds; parse
    # any sub-microsecond digits ourselves to avoid losing precision.
    fraction = re.search(r'[0-9]{2}[.,]([0-9]{7,})', rfc3339)
    if fraction:
      digits = fraction.group(1)
      if len(digits) > cls.NANOS_PRECISION:
        raise ValueError(
            "Could not parse RFC 3339 string '%s': fractional seconds "
            'beyond nanosecond precision are not supported.' % rfc3339)
      precision = len(digits)
      sub_micro = int(digits[cls.MICROS_PRECISION:])
      return Timestamp(
          timestamp.seconds(),
          timestamp.subseconds() * _POW_10[precision - cls.MICROS_PRECISION] +
          sub_micro,

View on GitHub (pinned to 12126d8942)