apache/beam · error · ValueError
%r cannot be represented exactly at precision
Error message
%r cannot be represented exactly at precision %d. Set allow_lossy_conversion=True to truncate it.
What it means
Timestamp.to_precision() refuses to narrow a timestamp's precision when doing so would discard non-zero digits, unless you pass allow_lossy_conversion=True. This prevents silently losing sub-unit data when truncating (flooring) to a coarser precision.
Solutions
- Pass allow_lossy_conversion=True if truncation is acceptable: ts.to_precision(6, allow_lossy_conversion=True).
- Check first whether the conversion is lossless (e.g. compare nanos % 1000 == 0) and handle the remainder explicitly.
- Keep the timestamp at its original precision and only round at output/formatting time (to_rfc3339).
Example fix
// before micro_ts = nano_ts.to_precision(6) // after micro_ts = nano_ts.to_precision(6, allow_lossy_conversion=True)
Defensive patterns
Strategy: validation
Validate before calling
def truncatable(ts, target):
return ts.precision <= target or ts.nanos % (10 ** (ts.precision - target)) == 0 Try / catch
try:
return ts.to_precision(target)
except ValueError:
return ts.to_precision(target, allow_lossy_conversion=True) Prevention
- Decide your truncation policy once and pass allow_lossy_conversion=True explicitly.
- Check for a non-zero remainder before narrowing precision if losslessness matters.
- Keep full precision internally; round only at output boundaries.
When it happens
Trigger: Calling ts.to_precision(p) with p < ts._precision when the truncated subsecond digits are non-zero and allow_lossy_conversion is not set, e.g. Timestamp(0, 500_000_000, 9).to_precision(6).
Common situations: Downsampling nanosecond-precision event timestamps to microsecond windows; converting beam timestamps to external systems that only store micros; user code that assumes truncation is silent like Python's int().
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
- Converting %r to datetime truncates it to microseconds. Set…
- %r has greater than microsecond precision, converting it to…
- The difference of %r and %r has sub-microsecond precision…
- The remainder of %r modulo %r has sub-microsecond…
- Bad timestamp value for message
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/cc6aa25321ac26fe.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/python/apache_beam/utils/timestamp.py:267
Increasing precision is always lossless. Decreasing precision raises
ValueError if this timestamp has a non-zero component below the target
precision, unless allow_lossy_conversion is True, in which case the
timestamp is truncated (floored) to the target precision.
"""
if precision == self._precision:
return self
if not 0 <= precision <= Timestamp.NANOS_PRECISION:
raise ValueError(
'Timestamp precision must be between 0 and %d (inclusive), '
'but was %d.' % (Timestamp.NANOS_PRECISION, precision))
if precision > self._precision:
scale = _POW_10[precision - self._precision]
return Timestamp(self._seconds, self._subseconds * scale, precision)
scale = _POW_10[self._precision - precision]
remainder = self._subseconds % scale
if remainder and not allow_lossy_conversion:
raise ValueError(
'%r cannot be represented exactly at precision %d. Set '
'allow_lossy_conversion=True to truncate it.' % (self, precision))
return Timestamp(self._seconds, self._subseconds // scale, precision)
def predecessor(self) -> 'Timestamp':
"""Returns the largest timestamp smaller than self, at this precision."""
return Timestamp(self._seconds, self._subseconds - 1, self._precision)
def successor(self) -> 'Timestamp':
"""Returns the smallest timestamp larger than self, at this precision."""
return Timestamp(self._seconds, self._subseconds + 1, self._precision)
def __repr__(self) -> str:
total = self._total(self._precision)
sign = ''
if total < 0:
sign = '-'
total = -totalView on GitHub (pinned to 12126d8942)