{"record":{"id":"dc733a1d031756e3","repo":"apache/beam","slug":"cannot-convert-from-nanoseconds-to-microseconds-because-this","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/utils/timestamp.py","lineNumber":536,"sourceCode":"  def to_proto(self) -> duration_pb2.Duration:\n    \"\"\"Returns the `google.protobuf.duration_pb2` representation.\"\"\"\n    secs = self.micros // 1000000\n    nanos = (self.micros % 1000000) * 1000\n    return duration_pb2.Duration(seconds=secs, nanos=nanos)\n\n  @staticmethod\n  def from_proto(duration_proto: duration_pb2.Duration) -> 'Duration':\n    \"\"\"Creates a Duration from a `google.protobuf.duration_pb2`.\n\n    Note that the google has a sub-second resolution of nanoseconds whereas this\n    class has a resolution of microsends. This class will truncate the\n    nanosecond resolution down to the microsecond.\n    \"\"\"\n\n    if duration_proto.nanos % 1000 != 0:\n      # TODO(https://github.com/apache/beam/issues/19922): Better define\n      # durations.\n      raise ValueError(\n          \"Cannot convert from nanoseconds to microseconds \" +\n          \"because this loses precision. Please make sure that \" +\n          \"this is the correct behavior you want and manually \" +\n          \"truncate the precision to the nearest microseconds. \" +\n          \"See [https://github.com/apache/beam/issues/19922] for \" +\n          \"more information.\")\n\n    return Duration(\n        seconds=duration_proto.seconds, micros=duration_proto.nanos // 1000)\n\n  def __repr__(self) -> str:\n    micros = self.micros\n    sign = ''\n    if micros < 0:\n      sign = '-'\n      micros = -micros\n    int_part = micros // 1000000\n    frac_part = micros % 1000000","sourceCodeStart":518,"sourceCodeEnd":554,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/utils/timestamp.py#L518-L554","documentation":"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.","triggerScenarios":"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)).","commonSituations":"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.","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."],"exampleFix":"// before\nd = Duration.from_proto(duration_pb2.Duration(seconds=1, nanos=123456789))\n// after\nproto = duration_pb2.Duration(seconds=1, nanos=123456789)\nproto.nanos = (proto.nanos // 1000) * 1000  # truncate to micros\nd = Duration.from_proto(proto)","handlingStrategy":"validation","validationCode":"def from_proto_safe(proto):\n    if proto.nanos % 1000 != 0:\n        proto = duration_pb2.Duration(seconds=proto.seconds,\n                                      nanos=(proto.nanos // 1000) * 1000)\n    return Duration.from_proto(proto)","typeGuard":"def proto_is_micro_aligned(proto):\n    return proto.nanos % 1000 == 0","tryCatchPattern":"try:\n    d = Duration.from_proto(proto)\nexcept ValueError:\n    d = Duration(micros=proto.seconds * 1_000_000 + proto.nanos // 1000)","preventionTips":["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."],"tags":["python","apache-beam","protobuf","duration","precision-loss"],"backgroundTag":"precision-loss","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}