]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix: fix return value of `DateFromTicks` and `TimeFromTicks` 1061/head
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 1 May 2025 17:38:18 +0000 (19:38 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 4 May 2025 10:58:44 +0000 (12:58 +0200)
These function are based on the return value of `TimestampFromTicks`.
Because this value returned by this function is timezone-aware and
localized to the current timezone, truncating the value (and implicitly
losing the timezone) might return the wrong result.

For example, at tick 0, in NY, it was 1969-12-31T19:00-05:00. By
truncating the timestamp on the date, the date would be the 1969-12-31
instead of the expected 1970-01-01.

This changeset actually changes `TimestampFromTicks` to return an UTC
timestamp instead of a local timestamp. We believe that this change is
not a problem because we don't change neither the type (tz-aware
datetime) nor the value of the returned object (old and new values
compare equal). An alternative would have been to decouple the Timestamp
function from the Date/Time functions, but I think it would be confusing
if the Timestamp function returned a datetime with the date in 1969 but the
Date function returned a date in 1970.

Adjust the tests to be *stricter* than before, making use of the fact
that now the output is not affected by the local timezone.

Close #1058.

docs/news.rst
psycopg/psycopg/dbapi20.py
tests/test_psycopg_dbapi20.py

index 8d4ace435d2429588529dd5361ef7e06527b3b27..22d51b7d9785f6025d76993a94312f54db1f1335 100644 (file)
@@ -15,6 +15,14 @@ Psycopg 3.3.0 (unreleased)
 
 - Drop support for Python 3.8.
 
+Psycopg 3.2.8 (unreleased)
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fix `DateFromTicks` and `TimeFromTicks` return values to return date and
+  time referred as UTC rather than the local timezone. Change
+  `TimestampFromTicks` to return a datetime in UTC rather than in the local
+  timezone (:ticket:`#1058`).
+
 
 Psycopg 3.2.7
 ^^^^^^^^^^^^^
index c80b7e5e405c9c25135feb408e862e414a9e0b8f..bffcf327cb23c0df554892d746eb280bba922622 100644 (file)
@@ -6,9 +6,7 @@ Compatibility objects with DBAPI 2.0
 
 from __future__ import annotations
 
-import time
 import datetime as dt
-from math import floor
 from typing import Any
 from collections.abc import Sequence
 
@@ -16,6 +14,8 @@ from . import _oids
 from .abc import AdaptContext, Buffer
 from .types.string import BytesBinaryDumper, BytesDumper
 
+EPOCH = dt.datetime(1970, 1, 1, tzinfo=dt.timezone.utc)
+
 
 class DBAPITypeObject:
     def __init__(self, name: str, oids: Sequence[int]):
@@ -116,12 +116,7 @@ def Timestamp(
 
 
 def TimestampFromTicks(ticks: float) -> dt.datetime:
-    secs = floor(ticks)
-    frac = ticks - secs
-    t = time.localtime(ticks)
-    tzinfo = dt.timezone(dt.timedelta(seconds=t.tm_gmtoff))
-    rv = dt.datetime(*t[:6], round(frac * 1_000_000), tzinfo=tzinfo)
-    return rv
+    return EPOCH + dt.timedelta(seconds=ticks)
 
 
 def register_dbapi20_adapters(context: AdaptContext) -> None:
index 768b98f8b0d0d133cdc00fb1395f391d425b3c98..216d9de86cd1dae18c7bf704b0fd8cddce087613 100644 (file)
@@ -84,6 +84,7 @@ def test_singletons(conn, typename, singleton):
     "ticks, want",
     [
         (0, "1970-01-01T00:00:00.000000+0000"),
+        (86370, "1970-01-01T23:59:30.000000+0000"),
         (1273173119.99992, "2010-05-06T14:11:59.999920-0500"),
     ],
 )
@@ -97,26 +98,24 @@ def test_timestamp_from_ticks(ticks, want):
     "ticks, want",
     [
         (0, "1970-01-01"),
-        # Returned date is local
-        (1273173119.99992, ["2010-05-06", "2010-05-07"]),
+        (86370, "1970-01-01"),  # 30 sec from Jan the 2nd, to test East localization
+        (1273173119.99992, "2010-05-06"),
     ],
 )
 def test_date_from_ticks(ticks, want):
     s = psycopg.DateFromTicks(ticks)
-    if isinstance(want, str):
-        want = [want]
-    want = [dt.datetime.strptime(w, "%Y-%m-%d").date() for w in want]
-    assert s in want
+    want = dt.datetime.strptime(want, "%Y-%m-%d").date()
+    assert s == want
 
 
 @pytest.mark.parametrize(
     "ticks, want",
-    [(0, "00:00:00.000000"), (1273173119.99992, "00:11:59.999920")],
+    [(0, "00:00:00.000000"), (1273173119.99992, "19:11:59.999920")],
 )
 def test_time_from_ticks(ticks, want):
     s = psycopg.TimeFromTicks(ticks)
     want = dt.datetime.strptime(want, "%H:%M:%S.%f").time()
-    assert s.replace(hour=0) == want
+    assert s == want
 
 
 @pytest.mark.parametrize(