From: Daniele Varrazzo Date: Mon, 26 Oct 2020 23:15:03 +0000 (+0100) Subject: Raise DataError for dates and times that can't be represented X-Git-Tag: 3.0.dev0~432 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=46fa4341612d45257e6d1075febd9db7cd3659e3;p=thirdparty%2Fpsycopg.git Raise DataError for dates and times that can't be represented --- diff --git a/psycopg3/psycopg3/types/date.py b/psycopg3/psycopg3/types/date.py index afbbc2ff0..9837ffd1b 100644 --- a/psycopg3/psycopg3/types/date.py +++ b/psycopg3/psycopg3/types/date.py @@ -10,7 +10,7 @@ from typing import cast from ..adapt import Dumper, Loader from ..proto import AdaptContext -from ..errors import InterfaceError +from ..errors import InterfaceError, DataError from .oids import builtins @@ -103,25 +103,25 @@ class DateLoader(Loader): # Most likely we received a BC date, which Python doesn't support # Otherwise the unexpected value is displayed in the exception. if data.endswith(b"BC"): - raise ValueError( + raise DataError( "Python doesn't support BC date:" f" got {data.decode('utf8', 'replace')}" ) - # Find the year from the date. We check if >= Y10K only in ISO format, - # others are too silly to bother being polite. - ds = self._get_datestyle() - if ds.startswith(b"ISO"): - year = int(data.split(b"-", 1)[0]) - if year > 9999: - raise ValueError( - "Python date doesn't support years after 9999:" - f" got {data.decode('utf8', 'replace')}" - ) + if self._get_year_digits(data) > 4: + raise DataError( + "Python date doesn't support years after 9999:" + f" got {data.decode('utf8', 'replace')}" + ) # We genuinely received something we cannot parse raise exc + def _get_year_digits(self, data: bytes) -> int: + datesep = self._format[2].encode("ascii") + parts = data.split(b" ")[0].split(datesep) + return max(map(len, parts)) + @Loader.text(builtins["time"].oid) class TimeLoader(Loader): @@ -141,7 +141,9 @@ class TimeLoader(Loader): def _raise_error(self, data: bytes, exc: ValueError) -> time: # Most likely, time 24:00 if data.startswith(b"24"): - raise ValueError("time with hour 24 not supported by Python") + raise DataError( + f"time not supported by Python: {data.decode('ascii')}" + ) # We genuinely received something we cannot parse raise exc @@ -187,6 +189,17 @@ class TimestampLoader(DateLoader): def _raise_error(self, data: bytes, exc: ValueError) -> datetime: return cast(datetime, super()._raise_error(data, exc)) + def _get_year_digits(self, data: bytes) -> int: + # Find the year from the date. + if not self._get_datestyle().startswith(b"P"): # Postgres + return super()._get_year_digits(data) + else: + parts = data.split() + if len(parts) > 4: + return len(parts[4]) + else: + return 0 + @Loader.text(builtins["timestamptz"].oid) class TimestamptzLoader(TimestampLoader): diff --git a/tests/types/test_date.py b/tests/types/test_date.py index eaddb51e4..fc154c9d7 100644 --- a/tests/types/test_date.py +++ b/tests/types/test_date.py @@ -1,6 +1,7 @@ import datetime as dt import pytest +from psycopg3 import DataError from psycopg3.adapt import Format @@ -86,7 +87,7 @@ def test_load_date_bc(conn, datestyle_out): cur = conn.cursor() cur.execute(f"set datestyle = {datestyle_out}, YMD") cur.execute("select %s - 1", (dt.date.min,)) - with pytest.raises(ValueError): + with pytest.raises(DataError): cur.fetchone()[0] @@ -95,7 +96,7 @@ def test_load_date_too_large(conn, datestyle_out): cur = conn.cursor() cur.execute(f"set datestyle = {datestyle_out}, YMD") cur.execute("select %s + 1", (dt.date.max,)) - with pytest.raises(ValueError): + with pytest.raises(DataError): cur.fetchone()[0] @@ -191,6 +192,24 @@ def test_load_datetime(conn, val, expr, datestyle_in, datestyle_out): assert cur.fetchone()[0] == as_dt(val) +@pytest.mark.parametrize("datestyle_out", ["ISO", "Postgres", "SQL", "German"]) +def test_load_datetime_bc(conn, datestyle_out): + cur = conn.cursor() + cur.execute(f"set datestyle = {datestyle_out}, YMD") + cur.execute("select %s - '1s'::interval", (dt.datetime.min,)) + with pytest.raises(DataError): + cur.fetchone()[0] + + +@pytest.mark.parametrize("datestyle_out", ["ISO", "SQL", "Postgres", "German"]) +def test_load_datetime_too_large(conn, datestyle_out): + cur = conn.cursor() + cur.execute(f"set datestyle = {datestyle_out}, YMD") + cur.execute("select %s + '1s'::interval", (dt.datetime.max,)) + with pytest.raises(DataError): + cur.fetchone()[0] + + # # datetime+tz tests # @@ -330,5 +349,5 @@ def test_load_time_binary(conn, val, expr): def test_load_time_24(conn): cur = conn.cursor() cur.execute("select '24:00'::time") - with pytest.raises(ValueError): + with pytest.raises(DataError): cur.fetchone()[0]