]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Raise a polite exception for BC dates
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 23 Aug 2020 20:13:34 +0000 (21:13 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 28 Oct 2020 03:19:24 +0000 (04:19 +0100)
psycopg3/psycopg3/types/date.py
tests/types/test_date.py

index adff96076c93a782db50b0942430544d64ef8219..6a7d0b538b1fbba153bbeff14895934df0e56e4b 100644 (file)
@@ -9,6 +9,7 @@ from datetime import date
 
 from ..adapt import Dumper, Loader
 from ..proto import AdaptContext
+from ..errors import InterfaceError
 from .oids import builtins
 
 
@@ -39,19 +40,44 @@ class DateLoader(Loader):
         if self.connection:
             ds = self.connection.pgconn.parameter_status(b"DateStyle")
             if not ds or ds.startswith(b"ISO"):
-                pass
+                pass    # Default: YMD
             elif ds.startswith(b"German"):
                 self.load = self.load_dmy  # type: ignore
-            elif ds.startswith(b"SQL"):
-                self.load = self.load_mdy  # type: ignore
-            elif ds.startswith(b"Postgres"):
+            elif ds.startswith(b"SQL") or ds.startswith(b"Postgres"):
                 self.load = self.load_mdy  # type: ignore
 
-    def load(self, data: bytes) -> date:
-        return date(int(data[:4]), int(data[5:7]), int(data[8:]))
+    def load_ymd(self, data: bytes) -> date:
+        try:
+            return date(int(data[:4]), int(data[5:7]), int(data[8:]))
+        except ValueError as e:
+            exc = e
+
+        return self._raise_error(data, exc)
+
+    load = load_ymd
 
     def load_dmy(self, data: bytes) -> date:
-        return date(int(data[6:]), int(data[3:5]), int(data[:2]))
+        try:
+            return date(int(data[6:]), int(data[3:5]), int(data[:2]))
+        except ValueError as e:
+            exc = e
+
+        return self._raise_error(data, exc)
 
     def load_mdy(self, data: bytes) -> date:
-        return date(int(data[6:]), int(data[:2]), int(data[3:5]))
+        try:
+            return date(int(data[6:]), int(data[:2]), int(data[3:5]))
+        except ValueError as e:
+            exc = e
+
+        return self._raise_error(data, exc)
+
+    def _raise_error(self, data: bytes, exc: ValueError) -> date:
+        # 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 InterfaceError(
+                "BC date not supported by Python:"
+                f" {data.decode('utf8', 'replace')}")
+        else:
+            raise exc
index 07fe9a0cbb136ad2787c4551a229f2465ae40c64..dcfdbadb2262d2c0b7389fb55565253e7ec509e3 100644 (file)
@@ -1,17 +1,20 @@
 import datetime as dt
 import pytest
 
+import psycopg3
 from psycopg3.adapt import Format
 
 
 @pytest.mark.parametrize(
     "val, expr",
     [
+        (dt.date.min, "'0001-01-01'::date"),
         (dt.date(1, 1, 1), "'0001-01-01'::date"),
         (dt.date(1000, 1, 1), "'1000-01-01'::date"),
         (dt.date(2000, 1, 1), "'2000-01-01'::date"),
         (dt.date(2000, 12, 31), "'2000-12-31'::date"),
         (dt.date(3000, 1, 1), "'3000-01-01'::date"),
+        (dt.date.max, "'9999-12-31'::date"),
     ],
 )
 def test_dump_date(conn, val, expr):
@@ -70,3 +73,12 @@ def test_load_date_datestyle(conn, datestyle_out):
     cur.execute(f"set datestyle = {datestyle_out}, YMD")
     cur.execute("select '2000-01-02'::date")
     assert cur.fetchone()[0] == dt.date(2000, 1, 2)
+
+
+@pytest.mark.parametrize("datestyle_out", ["ISO", "Postgres", "SQL", "German"])
+def test_load_date_bc(conn, datestyle_out):
+    cur = conn.cursor()
+    cur.execute(f"set datestyle = {datestyle_out}, YMD")
+    cur.execute("select '0001-01-01'::date - 1")
+    with pytest.raises(psycopg3.InterfaceError):
+        cur.fetchone()[0]