{"record":{"id":"b053b5e16cc4bb22","repo":"apache/beam","slug":"the-difference-of-r-and-r-has-sub-microsecond-precision","errorCode":null,"errorMessage":"The difference of %r and %r has sub-microsecond precision, which Duration cannot represent. Truncate the operands with to_precision(6, allow_lossy_conversion=True) first.","messagePattern":"The difference of %r and %r has sub-microsecond precision, which Duration cannot represent\\. Truncate the operands with to_precision\\(6, allow_lossy_conversion=True\\) first\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/utils/timestamp.py","lineNumber":452,"sourceCode":"    return self + other\n\n  @overload\n  def __sub__(self, other: DurationTypes) -> 'Timestamp':\n    pass\n\n  @overload\n  def __sub__(self, other: 'Timestamp') -> 'Duration':\n    pass\n\n  def __sub__(\n      self, other: Union[DurationTypes,\n                         'Timestamp']) -> Union['Timestamp', 'Duration']:\n    if isinstance(other, Timestamp):\n      diff_nanos = (\n          self._total(Timestamp.NANOS_PRECISION) -\n          other._total(Timestamp.NANOS_PRECISION))\n      if diff_nanos % 1000 != 0:\n        raise ValueError(\n            'The difference of %r and %r has sub-microsecond precision, '\n            'which Duration cannot represent. Truncate the operands with '\n            'to_precision(6, allow_lossy_conversion=True) first.' %\n            (self, other))\n      return Duration(micros=diff_nanos // 1000)\n    other = Duration.of(other)\n    precision = max(self._precision, Timestamp.MICROS_PRECISION)\n    return Timestamp(\n        subseconds=self._total(precision) -\n        other.micros * _POW_10[precision - Timestamp.MICROS_PRECISION],\n        precision=precision)\n\n  def __mod__(self, other: DurationTypes) -> 'Duration':\n    other = Duration.of(other)\n    remainder_nanos = self._total(Timestamp.NANOS_PRECISION) % (\n        other.micros * 1000)\n    if remainder_nanos % 1000 != 0:\n      raise ValueError(","sourceCodeStart":434,"sourceCodeEnd":470,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/utils/timestamp.py#L434-L470","documentation":"Subtracting one Timestamp from another computes the difference at nanosecond resolution, but Duration only stores microseconds. If the difference has sub-microsecond digits, Timestamp.__sub__ raises ValueError because a Duration cannot represent it exactly.","triggerScenarios":"Evaluating ts1 - ts2 where the two timestamps differ by a non-multiple of 1000 nanoseconds, e.g. subtracting two nanosecond-precision timestamps such as Timestamp(0, 1_000_000_001, 9) - Timestamp(0, 0, 9).","commonSituations":"Measuring event-to-event latencies from nanosecond-resolution sources; test code computing expected durations from high-precision timestamps; porting code that previously only used microsecond timestamps.","solutions":["Truncate both operands first: ts1.to_precision(6, allow_lossy_conversion=True) - ts2.to_precision(6, allow_lossy_conversion=True).","Use `.nanos` on both timestamps and compute the difference as an integer nanosecond value yourself.","Construct the source timestamps at microsecond precision so differences are always representable.","Wrap the subtraction in try/except ValueError and fall back to a truncated-difference computation."],"exampleFix":"// before\ndelta = ts_end - ts_start  # ValueError for sub-microsecond diff\n// after\ndelta = (ts_end.to_precision(6, allow_lossy_conversion=True) -\n         ts_start.to_precision(6, allow_lossy_conversion=True))","handlingStrategy":"try-catch","validationCode":"def safe_sub(a, b):\n    diff = a.nanos - b.nanos\n    if diff % 1000:\n        raise ValueError(f'{diff} ns not representable as Duration micros')\n    return Duration(micros=diff // 1000)","typeGuard":null,"tryCatchPattern":"try:\n    delta = ts_end - ts_start\nexcept ValueError:\n    delta = (ts_end.to_precision(6, allow_lossy_conversion=True) -\n             ts_start.to_precision(6, allow_lossy_conversion=True))","preventionTips":["Compute with `.nanos` when operands may carry nanosecond precision.","Truncate to micros at ingest so timestamp arithmetic is always lossless.","Never assume Timestamp differences are microsecond-aligned."],"tags":["python","apache-beam","timestamp","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"}