apache/beam · error · ValueError
Cannot convert from nanoseconds to microseconds because…
Error message
Cannot convert from nanoseconds to microseconds because this loses precision. Please make sure that this is the correct behavior you want and manually truncate the precision to the nearest microseconds. See [https://github.com/apache/beam/issues/19922] for more information.
What it means
Duration.from_proto() converts a google.protobuf duration_pb2.Duration (seconds + nanos) into a Beam Duration, which only has microsecond resolution. If the proto's nanos field is not a multiple of 1000, the conversion would lose precision, so a ValueError is raised; the message links to beam issue 19922 about better defining duration precision.
Solutions
- Round/truncate the proto before converting: set nanos = (nanos // 1000) * 1000, or construct Duration directly with Duration(micros=(proto.seconds*1_000_000 + proto.nanos//1000)).
- Fix the producer so durations are emitted with microsecond resolution.
- If the caller controls the source, use Timestamp/Duration APIs end-to-end instead of passing nanosecond protos.
- Catch ValueError around from_proto and handle the lossy conversion explicitly for your domain.
Example fix
// before d = Duration.from_proto(duration_pb2.Duration(seconds=1, nanos=123456789)) // after proto = duration_pb2.Duration(seconds=1, nanos=123456789) proto.nanos = (proto.nanos // 1000) * 1000 # truncate to micros d = Duration.from_proto(proto)
Defensive patterns
Strategy: validation
Validate before calling
def from_proto_safe(proto):
if proto.nanos % 1000 != 0:
proto = duration_pb2.Duration(seconds=proto.seconds,
nanos=(proto.nanos // 1000) * 1000)
return Duration.from_proto(proto) Type guard
def proto_is_micro_aligned(proto):
return proto.nanos % 1000 == 0 Try / catch
try:
d = Duration.from_proto(proto)
except ValueError:
d = Duration(micros=proto.seconds * 1_000_000 + proto.nanos // 1000) Prevention
- Ask producers to emit microsecond-aligned durations.
- Truncate/round nanos at the protobuf boundary before conversion.
- Track beam issue 19922 for changes to duration precision semantics.
When it happens
Trigger: Calling Duration.from_proto(proto) where proto.nanos % 1000 != 0, e.g. protos built from external systems with nanosecond durations (proto.FromTimedelta with nanos, or hand-built duration_pb2.Duration(seconds=1, nanos=123)).
Common situations: Decoding durations from gRPC/protobuf payloads produced by services that emit true nanosecond durations; converting from other timestamp libraries; test fixtures with arbitrary nanos values.
Related errors
- The difference of %r and %r has sub-microsecond precision…
- The remainder of %r modulo %r has sub-microsecond…
- Cannot interpret as Duration.
- Converting %r to datetime truncates it to microseconds. Set…
- Could not find enum descriptor
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/dc733a1d031756e3.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/python/apache_beam/utils/timestamp.py:536
def to_proto(self) -> duration_pb2.Duration:
"""Returns the `google.protobuf.duration_pb2` representation."""
secs = self.micros // 1000000
nanos = (self.micros % 1000000) * 1000
return duration_pb2.Duration(seconds=secs, nanos=nanos)
@staticmethod
def from_proto(duration_proto: duration_pb2.Duration) -> 'Duration':
"""Creates a Duration from a `google.protobuf.duration_pb2`.
Note that the google has a sub-second resolution of nanoseconds whereas this
class has a resolution of microsends. This class will truncate the
nanosecond resolution down to the microsecond.
"""
if duration_proto.nanos % 1000 != 0:
# TODO(https://github.com/apache/beam/issues/19922): Better define
# durations.
raise ValueError(
"Cannot convert from nanoseconds to microseconds " +
"because this loses precision. Please make sure that " +
"this is the correct behavior you want and manually " +
"truncate the precision to the nearest microseconds. " +
"See [https://github.com/apache/beam/issues/19922] for " +
"more information.")
return Duration(
seconds=duration_proto.seconds, micros=duration_proto.nanos // 1000)
def __repr__(self) -> str:
micros = self.micros
sign = ''
if micros < 0:
sign = '-'
micros = -micros
int_part = micros // 1000000
frac_part = micros % 1000000View on GitHub (pinned to 12126d8942)