]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Drop floating point dumping of datetime objects
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 16 May 2021 07:33:44 +0000 (09:33 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 16 May 2021 07:34:49 +0000 (09:34 +0200)
The random test found rounding errors around year 2034.

psycopg3/psycopg3/types/date.py
tests/types/test_date.py

index 6548bc801157efcd9d8c7cb3f77e456ff911d610..430294c83df6e8f66dfe959f8550fbdf826eaedd 100644 (file)
@@ -178,21 +178,11 @@ class DateTimeTzBinaryDumper(_BaseDateTimeDumper):
 
     format = Format.BINARY
 
-    # Somewhere, between year 2270 and 2275, float rounding in total_seconds
-    # cause us errors: switch to an algorithm without rounding before then.
-    _delta_prec_loss = (
-        datetime(2250, 1, 1) - _pg_datetime_epoch
-    ).total_seconds()
-
     def dump(self, obj: datetime) -> bytes:
         delta = obj - _pg_datetimetz_epoch
-        secs = delta.total_seconds()
-        if -self._delta_prec_loss < secs < self._delta_prec_loss:
-            micros = int(1_000_000 * secs)
-        else:
-            micros = delta.microseconds + 1_000_000 * (
-                86_400 * delta.days + delta.seconds
-            )
+        micros = delta.microseconds + 1_000_000 * (
+            86_400 * delta.days + delta.seconds
+        )
         return _pack_int8(micros)
 
     def upgrade(self, obj: datetime, format: Pg3Format) -> "Dumper":
@@ -207,14 +197,10 @@ class DateTimeBinaryDumper(DateTimeTzBinaryDumper):
 
     def dump(self, obj: datetime) -> bytes:
         delta = obj - _pg_datetime_epoch
-        secs = delta.total_seconds()
-        if -self._delta_prec_loss < secs < self._delta_prec_loss:
-            micros = int(1_000_000 * secs)
-        else:
-            micros = (
-                1_000_000 * (86_400 * delta.days + delta.seconds)
-                + delta.microseconds
-            )
+        micros = (
+            1_000_000 * (86_400 * delta.days + delta.seconds)
+            + delta.microseconds
+        )
         return _pack_int8(micros)
 
 
index f22c70bffca93c0fdc36af1976261c879b1594f5..3d094ffbe35ecab7a971ac7ad8e751ae7f38068d 100644 (file)
@@ -103,6 +103,7 @@ class TestDatetime:
             ("2000,1,2,3,4,5,678", "2000-01-02 03:04:05.000678"),
             ("2000,1,2,3,0,0,456789", "2000-01-02 03:00:00.456789"),
             ("2000,1,1,0,0,0,1", "2000-01-01 00:00:00.000001"),
+            ("2034,02,03,23,34,27,951357", "2034-02-03 23:34:27.951357"),
             ("2200,1,1,0,0,0,1", "2200-01-01 00:00:00.000001"),
             ("2300,1,1,0,0,0,1", "2300-01-01 00:00:00.000001"),
             ("7000,1,1,0,0,0,1", "7000-01-01 00:00:00.000001"),
@@ -117,7 +118,15 @@ class TestDatetime:
         cur.execute("set timezone to '+02:00'")
         cur.execute(f"select %{fmt_in}", (as_dt(val),))
         cur.execute(f"select '{expr}'::timestamp = %{fmt_in}", (as_dt(val),))
-        assert cur.fetchone()[0] is True
+        cur.execute(
+            f"""
+            select '{expr}'::timestamp = %(val){fmt_in},
+            '{expr}', %(val){fmt_in}::text
+            """,
+            {"val": as_dt(val)},
+        )
+        ok, want, got = cur.fetchone()
+        assert ok, (want, got)
 
     @pytest.mark.parametrize("datestyle_in", ["DMY", "MDY", "YMD"])
     def test_dump_datetime_datestyle(self, conn, datestyle_in):
@@ -210,6 +219,10 @@ class TestDateTimeTz:
             ("2000,1,1,0,0~01:02:03", "2000-01-01 00:00+01:02:03"),
             ("2000,1,1,0,0~-01:02:03", "2000-01-01 00:00-01:02:03"),
             ("2000,12,31,23,59,59,999999~2", "2000-12-31 23:59:59.999999+2"),
+            (
+                "2034,02,03,23,34,27,951357~-4:27",
+                "2034-02-03 23:34:27.951357-04:27",
+            ),
             ("2300,1,1,0,0,0,1~1", "2300-01-01 00:00:00.000001+1"),
             ("3000,1,1,0,0~2", "3000-01-01 00:00+2"),
             ("7000,1,1,0,0,0,1~-1:2:3", "7000-01-01 00:00:00.000001-01:02:03"),
@@ -222,8 +235,15 @@ class TestDateTimeTz:
     def test_dump_datetimetz(self, conn, val, expr, fmt_in):
         cur = conn.cursor()
         cur.execute("set timezone to '-02:00'")
-        cur.execute(f"select '{expr}'::timestamptz = %{fmt_in}", (as_dt(val),))
-        assert cur.fetchone()[0] is True
+        cur.execute(
+            f"""
+            select '{expr}'::timestamptz = %(val){fmt_in},
+            '{expr}', %(val){fmt_in}::text
+            """,
+            {"val": as_dt(val)},
+        )
+        ok, want, got = cur.fetchone()
+        assert ok, (want, got)
 
     @pytest.mark.parametrize("datestyle_in", ["DMY", "MDY", "YMD"])
     def test_dump_datetimetz_datestyle(self, conn, datestyle_in):