This is an automated email from the ASF dual-hosted git repository.

jrmccluskey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new 5d32ff75b4c Round float seconds to nearest subsecond unit in Timestamp 
constructor (#40257)
5d32ff75b4c is described below

commit 5d32ff75b4cbe117fd7f9a50a40abb9aab6d7d5e
Author: Deepika Mamidipelly <[email protected]>
AuthorDate: Thu Sep 24 08:11:40 2026 -0700

    Round float seconds to nearest subsecond unit in Timestamp constructor 
(#40257)
    
    * Round float seconds to nearest subsecond unit in Timestamp constructor
    
    Fixes #40235: the float-to-integer conversion used int(), which truncates
    toward zero, so inexactly-representable floats like 2.000002 landed one
    microsecond below the intended value. Using round() makes the conversion
    match the documented behavior ("the fractional part will be captured up to
    precision digits") and is symmetric about zero. Adds a regression test.
    
    * Fix yapf formatting in timestamp_test.py
    
    ---------
    
    Co-authored-by: Deepika Mamidipelly <[email protected]>
---
 sdks/python/apache_beam/utils/timestamp.py      |  7 +++++--
 sdks/python/apache_beam/utils/timestamp_test.py | 16 ++++++++++++++++
 2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/sdks/python/apache_beam/utils/timestamp.py 
b/sdks/python/apache_beam/utils/timestamp.py
index 925467044f3..c09cc93439c 100644
--- a/sdks/python/apache_beam/utils/timestamp.py
+++ b/sdks/python/apache_beam/utils/timestamp.py
@@ -64,7 +64,7 @@ class Timestamp(object):
   fraction of a second (e.g. 3 for millis, 6 for micros, 9 for
   nanos). Defaults to microseconds.
   If ``seconds`` is a float, the fractional part will be captured up
-  to ``precision`` digits.
+  to ``precision`` digits, rounded to the nearest subsecond unit.
 
   Lossy conversion operations will throw an error unless
   ``allow_lossy_conversion=True`` is specified (e.g. see ``to_utc_datetime``).
@@ -107,7 +107,10 @@ class Timestamp(object):
             'use subseconds instead.' % precision)
       subseconds = micros
     self._precision = precision
-    total = int(seconds * _POW_10[precision]) + int(subseconds)
+    # Round (rather than truncate) so that float inputs like 2.000002, which
+    # cannot be represented exactly in binary, land on the nearest subsecond
+    # unit instead of one unit low.
+    total = round(seconds * _POW_10[precision]) + int(subseconds)
     self._seconds, self._subseconds = divmod(total, _POW_10[precision])
 
   def _total(self, precision: int) -> int:
diff --git a/sdks/python/apache_beam/utils/timestamp_test.py 
b/sdks/python/apache_beam/utils/timestamp_test.py
index ec1c3604652..ff37ff84b5d 100644
--- a/sdks/python/apache_beam/utils/timestamp_test.py
+++ b/sdks/python/apache_beam/utils/timestamp_test.py
@@ -214,6 +214,22 @@ class TimestampPrecisionTest(unittest.TestCase):
     self.assertEqual(ts.subseconds(), 500000)
     self.assertEqual(ts, Timestamp(-1.5))
 
+  def test_constructor_float_rounds_to_nearest(self):
+    # Float seconds that are not exactly representable must round to the
+    # nearest subsecond unit rather than truncating one unit low.
+    self.assertEqual(Timestamp(2.000002).micros, 2000002)
+    self.assertEqual(Timestamp(1.000001).micros, 1000001)
+    self.assertEqual(Timestamp.of(2.000002).micros, 2000002)
+    # Rounding is symmetric about zero (truncation was not).
+    self.assertEqual(Timestamp(-2.000002).micros, -2000002)
+    self.assertEqual(Timestamp(-2.000002).micros, -Timestamp(2.000002).micros)
+    # Exact values are unaffected.
+    self.assertEqual(Timestamp(1.5).micros, 1500000)
+    self.assertEqual(Timestamp(2).micros, 2000000)
+    # Rounding at finer precisions.
+    ts = Timestamp(1.234567895, precision=9)
+    self.assertEqual(ts.nanos, 1234567895)
+
   def test_constructor_validation(self):
     with self.assertRaises(ValueError):
       Timestamp(0, 0, precision=10)

Reply via email to