From df3ba965b363a8783998916f2df4f8fa582d40c9 Mon Sep 17 00:00:00 2001 From: Vishnu Prakash Date: Sat, 13 Jun 2026 16:00:59 +0530 Subject: [PATCH 1/2] fix(datetime): raise specific 'Missing zone offset' error in timestamptz_to_nanos --- pyiceberg/utils/datetime.py | 2 +- tests/utils/test_datetime.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pyiceberg/utils/datetime.py b/pyiceberg/utils/datetime.py index b5cde34f26..e5fa2595fe 100644 --- a/pyiceberg/utils/datetime.py +++ b/pyiceberg/utils/datetime.py @@ -140,7 +140,7 @@ def timestamptz_to_nanos(timestamptz_str: str) -> int: ms_str = match.group(2) if match.group(2) else "" timestamptz_str_without_ns_str = match.group(1) + ms_str + match.group(4) return datetime_to_nanos(datetime.fromisoformat(timestamptz_str_without_ns_str)) + int(ns_str) - if ISO_TIMESTAMPTZ_NANO.fullmatch(timestamptz_str): + if ISO_TIMESTAMP_NANO.fullmatch(timestamptz_str): # When we can match a timestamp without a zone, we can give a more specific error raise ValueError(f"Missing zone offset: {timestamptz_str} (must be ISO-8601)") raise ValueError(f"Invalid timestamp with zone: {timestamptz_str} (must be ISO-8601)") diff --git a/tests/utils/test_datetime.py b/tests/utils/test_datetime.py index 673908e19f..b2ea3e1053 100644 --- a/tests/utils/test_datetime.py +++ b/tests/utils/test_datetime.py @@ -130,6 +130,16 @@ def test_timestamptz_to_nanos(timestamp: str, nanos: int) -> None: assert nanos == timestamptz_to_nanos(timestamp) +def test_timestamptz_to_nanos_missing_zone_offset() -> None: + with pytest.raises(ValueError, match="Missing zone offset: 2025-02-23T20:21:44.375612001"): + timestamptz_to_nanos("2025-02-23T20:21:44.375612001") + + +def test_timestamp_to_nanos_unexpected_zone_offset() -> None: + with pytest.raises(ValueError, match="Zone offset provided, but not expected: 2025-02-23T16:21:44.375612001-04:00"): + timestamp_to_nanos("2025-02-23T16:21:44.375612001-04:00") + + @pytest.mark.parametrize("nanos, micros", [(1510871468000001001, 1510871468000001), (-1510871468000001001, -1510871468000002)]) def test_nanos_to_micros(nanos: int, micros: int) -> None: assert micros == nanos_to_micros(nanos) From 6b9dededa2d8602f5a77752f9bfb880280b19166 Mon Sep 17 00:00:00 2001 From: Vishnu Prakash Date: Mon, 22 Jun 2026 12:14:53 +0530 Subject: [PATCH 2/2] test(datetime): group timestamp_to_nanos tests together --- tests/utils/test_datetime.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/utils/test_datetime.py b/tests/utils/test_datetime.py index b2ea3e1053..e9529b6d3e 100644 --- a/tests/utils/test_datetime.py +++ b/tests/utils/test_datetime.py @@ -118,6 +118,11 @@ def test_timestamp_to_nanos(timestamp: str, nanos: int) -> None: assert nanos == timestamp_to_nanos(timestamp) +def test_timestamp_to_nanos_unexpected_zone_offset() -> None: + with pytest.raises(ValueError, match="Zone offset provided, but not expected: 2025-02-23T16:21:44.375612001-04:00"): + timestamp_to_nanos("2025-02-23T16:21:44.375612001-04:00") + + @pytest.mark.parametrize( "timestamp, nanos", [ @@ -135,11 +140,6 @@ def test_timestamptz_to_nanos_missing_zone_offset() -> None: timestamptz_to_nanos("2025-02-23T20:21:44.375612001") -def test_timestamp_to_nanos_unexpected_zone_offset() -> None: - with pytest.raises(ValueError, match="Zone offset provided, but not expected: 2025-02-23T16:21:44.375612001-04:00"): - timestamp_to_nanos("2025-02-23T16:21:44.375612001-04:00") - - @pytest.mark.parametrize("nanos, micros", [(1510871468000001001, 1510871468000001), (-1510871468000001001, -1510871468000002)]) def test_nanos_to_micros(nanos: int, micros: int) -> None: assert micros == nanos_to_micros(nanos)