- State if the date is too small or too large, not just "not supported".
- Use similar messages in text and binary format.
- Avoid an overflow error with the infinity date in Python.
See #315
try:
return date(int(ye), int(mo), int(da))
- except ValueError as e:
+ except ValueError as ex:
s = bytes(data).decode("utf8", "replace")
- if len(s) != 10:
- raise DataError(f"date not supported: {s!r}") from None
- raise DataError(f"can't parse date {s!r}: {e}") from None
+ if s == "infinity" or (s and len(s.split()[0]) > 10):
+ raise DataError(f"date too large (after year 10K): {s!r}") from None
+ elif s == "-infinity" or "BC" in s:
+ raise DataError(f"date too small (before year 1): {s!r}") from None
+ else:
+ raise DataError(f"can't parse date {s!r}: {ex}") from None
class DateBinaryLoader(Loader):
days = unpack_int4(data)[0] + _pg_date_epoch_days
try:
return date.fromordinal(days)
- except ValueError:
+ except (ValueError, OverflowError):
if days < _py_date_min_days:
raise DataError("date too small (before year 1)") from None
else:
else:
raise e.InterfaceError(f"unexpected DateStyle: {ds.decode('ascii')}")
+ cdef object _error_date(self, const char *data, str msg):
+ s = bytes(data).decode("utf8", "replace")
+ if s == "infinity" or len(s.split()[0]) > 10:
+ raise e.DataError(f"date too large (after year 10K): {s!r}") from None
+ elif s == "-infinity" or "BC" in s:
+ raise e.DataError(f"date too small (before year 1): {s!r}") from None
+ else:
+ raise e.DataError(f"can't parse date {s!r}: {msg}") from None
+
cdef object cload(self, const char *data, size_t length):
if length != 10:
- s = bytes(data).decode("utf8", "replace")
- raise e.DataError(f"date not supported: {s!r}")
+ self._error_date(data, "unexpected length")
DEF NVALUES = 3
cdef int vals[NVALUES]
else:
return cdt.date_new(vals[2], vals[0], vals[1])
except ValueError as ex:
- s = bytes(data).decode("utf8", "replace")
- raise e.DataError(f"can't parse date {s!r}: {ex}") from None
+ self._error_date(data, str(ex))
@cython.final
with pytest.raises(DataError):
cur.fetchone()[0]
+ overflow_samples = [
+ ("-infinity", "date too small"),
+ ("1000-01-01 BC", "date too small"),
+ ("10000-01-01", "date too large"),
+ ("infinity", "date too large"),
+ ]
+
+ @pytest.mark.parametrize("datestyle_out", ["ISO", "Postgres", "SQL", "German"])
+ @pytest.mark.parametrize("val, msg", overflow_samples)
+ def test_load_overflow_message(self, conn, datestyle_out, val, msg):
+ cur = conn.cursor()
+ cur.execute(f"set datestyle = {datestyle_out}, YMD")
+ cur.execute("select %s::date", (val,))
+ with pytest.raises(DataError) as excinfo:
+ cur.fetchone()[0]
+ assert msg in str(excinfo.value)
+
+ @pytest.mark.parametrize("val, msg", overflow_samples)
+ def test_load_overflow_message_binary(self, conn, val, msg):
+ cur = conn.cursor(binary=True)
+ cur.execute("select %s::date", (val,))
+ with pytest.raises(DataError) as excinfo:
+ cur.fetchone()[0]
+ assert msg in str(excinfo.value)
+
def test_infinity_date_example(self, conn):
# NOTE: this is an example in the docs. Make sure it doesn't regress when
# adding binary datetime adapters