From: Daniele Varrazzo Date: Sun, 16 May 2021 07:33:44 +0000 (+0200) Subject: Drop floating point dumping of datetime objects X-Git-Tag: 3.0.dev0~41 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=68ee54a9cb7eb1dd980754418794ce63b71c485d;p=thirdparty%2Fpsycopg.git Drop floating point dumping of datetime objects The random test found rounding errors around year 2034. --- diff --git a/psycopg3/psycopg3/types/date.py b/psycopg3/psycopg3/types/date.py index 6548bc801..430294c83 100644 --- a/psycopg3/psycopg3/types/date.py +++ b/psycopg3/psycopg3/types/date.py @@ -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) diff --git a/tests/types/test_date.py b/tests/types/test_date.py index f22c70bff..3d094ffbe 100644 --- a/tests/types/test_date.py +++ b/tests/types/test_date.py @@ -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):