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)