anxkhn opened a new pull request, #3614:
URL: https://github.com/apache/iceberg-python/pull/3614

   
   ```markdown
   # Rationale for this change
   
   `timestamp_to_nanos` and `timestamptz_to_nanos` in 
`pyiceberg/utils/datetime.py`
   silently corrupt the nanosecond value for timestamp strings whose fractional
   second has 7 or 8 digits.
   
   `ISO_TIMESTAMP_NANO` / `ISO_TIMESTAMPTZ_NANO` split the fraction into
   `group(2)` `(.\d{1,6})?` (the microsecond digits) and `group(3)` `(\d{1,3})?`
   (the sub-microsecond digits, i.e. fraction positions 7-9). Those functions 
read
   `group(3)` and add it as a bare integer:
   
   ```python
   ns_str = match.group(3) or "0"
   ...
   return datetime_to_nanos(...) + int(ns_str)
   ```
   
   When the fraction has exactly 7 or 8 digits, `group(3)` is only 1 or 2 
digits and
   represents fraction positions 7-8, so it has to be right-padded to 3 digits 
before
   being interpreted as a number of nanoseconds. Without padding, the digits are
   scaled as if they were the last positions of the fraction, so the value is 
off by
   up to ~999 ns:
   
   ```python
   from pyiceberg.utils.datetime import timestamp_to_nanos
   
   timestamp_to_nanos("2025-02-23T20:21:44.3756127")
   # fraction .3756127 s == 375612700 ns
   # expected: 1740342104375612700
   # actual:   1740342104375612007   (off by -693 ns)
   
   timestamp_to_nanos("2025-02-23T20:21:44.37561278")
   # expected: 1740342104375612780
   # actual:   1740342104375612078   (off by -702 ns)
   ```
   
   6-digit and full 9-digit fractions were already correct, which is why the 
bug went
   unnoticed. `timestamptz_to_nanos` has the identical defect.
   
   # Are these changes tested?
   
   Yes. Both functions now right-pad `group(3)` to 3 digits with `ljust(3, "0")`
   before it is read as nanoseconds, which is correct for every fraction width 
(the
   `group(3) is None` case is unchanged: `"0".ljust(3, "0") == "000"`).
   
   `tests/utils/test_datetime.py::test_timestamp_to_nanos` and
   `::test_timestamptz_to_nanos` previously only parametrized 6-digit and 
9-digit
   fractions, so the 7/8-digit boundary was untested. I added a 7-digit
   (`.3756127`) and an 8-digit (`.37561278`) case to each. Those new cases fail
   against the current code and pass with this fix; the full
   `tests/utils/test_datetime.py` file passes (53 tests), and `make lint` is 
clean.
   
   # Are there any user-facing changes?
   
   Yes, a bug fix: parsing a nanosecond timestamp string with a 7- or 8-digit
   fractional second now returns the correct nanosecond value instead of a 
silently
   corrupted one. There is no API change.
   ```
   
   <!-- Note for maintainers: this corrects silent numeric corruption on a 
public
        helper, so it may warrant the `changelog` label; I cannot set labels as 
an
        external contributor. -->
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to