Use datetime.timezone as default tzinfo_factory.

This commit is contained in:
Daniele Varrazzo 2021-06-14 23:46:39 +01:00
parent 2eac70786e
commit f28502663f
6 changed files with 47 additions and 15 deletions

2
NEWS
View File

@ -20,6 +20,8 @@ Other changes:
- Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`). - Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`).
- Dropped support for mx.DateTime. - Dropped support for mx.DateTime.
- Use `datetime.timezone` objects by default in datetime objects instead of
`~psycopg2.tz.FixedOffsetTimezone`.
- Build system for Linux/MacOS binary packages moved to GitHub action, now - Build system for Linux/MacOS binary packages moved to GitHub action, now
providing :pep:`600`\-style wheels packages. providing :pep:`600`\-style wheels packages.

View File

@ -498,8 +498,10 @@ The ``cursor`` class
The time zone factory used to handle data types such as The time zone factory used to handle data types such as
:sql:`TIMESTAMP WITH TIME ZONE`. It should be a `~datetime.tzinfo` :sql:`TIMESTAMP WITH TIME ZONE`. It should be a `~datetime.tzinfo`
object. A few implementations are available in the `psycopg2.tz` object. Default is `datetime.timezone`.
module.
.. versionchanged:: 2.9
previosly the default factory was `psycopg2.tz.FixedOffsetTimezone`.
.. method:: nextset() .. method:: nextset()

View File

@ -574,14 +574,13 @@ Time zones handling
''''''''''''''''''' '''''''''''''''''''
The PostgreSQL type :sql:`timestamp with time zone` (a.k.a. The PostgreSQL type :sql:`timestamp with time zone` (a.k.a.
:sql:`timestamptz`) is converted into Python `~datetime.datetime` objects with :sql:`timestamptz`) is converted into Python `~datetime.datetime` objects.
a `~datetime.datetime.tzinfo` attribute set to a
`~psycopg2.tz.FixedOffsetTimezone` instance.
>>> cur.execute("SET TIME ZONE 'Europe/Rome'") # UTC + 1 hour >>> cur.execute("SET TIME ZONE 'Europe/Rome'") # UTC + 1 hour
>>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz") >>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz")
>>> cur.fetchone()[0].tzinfo >>> cur.fetchone()[0]
psycopg2.tz.FixedOffsetTimezone(offset=60, name=None) datetime.datetime(2010, 1, 1, 10, 30, 45,
tzinfo=datetime.timezone(datetime.timedelta(seconds=3600)))
.. note:: .. note::
@ -594,9 +593,9 @@ a `~datetime.datetime.tzinfo` attribute set to a
>>> cur.execute("SELECT '1900-01-01 10:30:45'::timestamptz") >>> cur.execute("SELECT '1900-01-01 10:30:45'::timestamptz")
>>> cur.fetchone()[0].tzinfo >>> cur.fetchone()[0].tzinfo
# On Python 3.6: 5h, 21m # On Python 3.6: 5h, 21m
psycopg2.tz.FixedOffsetTimezone(offset=datetime.timedelta(0, 19260), name=None) datetime.timezone(datetime.timedelta(0, 19260))
# On Python 3.7 and following: 5h, 21m, 10s # On Python 3.7 and following: 5h, 21m, 10s
psycopg2.tz.FixedOffsetTimezone(offset=datetime.timedelta(seconds=19270), name=None) datetime.timezone(datetime.timedelta(seconds=19270))
.. versionchanged:: 2.2.2 .. versionchanged:: 2.2.2
timezones with seconds are supported (with rounding). Previously such timezones with seconds are supported (with rounding). Previously such
@ -605,6 +604,9 @@ a `~datetime.datetime.tzinfo` attribute set to a
.. versionchanged:: 2.9 .. versionchanged:: 2.9
timezones with seconds are supported without rounding. timezones with seconds are supported without rounding.
.. versionchanged:: 2.9
use `datetime.timezone` as default tzinfo object instead of
`~psycopg2.tz.FixedOffsetTimezone`.
.. index:: .. index::
double: Date objects; Infinite double: Date objects; Infinite

View File

@ -1951,10 +1951,11 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name)
/* default tzinfo factory */ /* default tzinfo factory */
{ {
/* The datetime api doesn't seem to have a constructor to make a
* datetime.timezone, so use the Python interface. */
PyObject *m = NULL; PyObject *m = NULL;
if ((m = PyImport_ImportModule("psycopg2.tz"))) { if ((m = PyImport_ImportModule("datetime"))) {
self->tzinfo_factory = PyObject_GetAttrString( self->tzinfo_factory = PyObject_GetAttrString(m, "timezone");
m, "FixedOffsetTimezone");
Py_DECREF(m); Py_DECREF(m);
} }
if (!self->tzinfo_factory) { if (!self->tzinfo_factory) {

View File

@ -104,9 +104,18 @@ _parse_inftz(const char *str, PyObject *curs)
goto exit; goto exit;
} }
if (!(tzinfo = PyObject_CallFunction(tzinfo_factory, "i", 0))) { #if PY_VERSION_HEX < 0x03070000
goto exit; {
PyObject *tzoff;
if (!(tzoff = PyDelta_FromDSU(0, 0, 0))) { goto exit; }
tzinfo = PyObject_CallFunctionObjArgs(tzinfo_factory, tzoff, NULL);
Py_DECREF(tzoff);
if (!tzinfo) { goto exit; }
} }
#else
tzinfo = PyDateTime_TimeZone_UTC;
Py_INCREF(tzinfo);
#endif
/* m.replace(tzinfo=tzinfo) */ /* m.replace(tzinfo=tzinfo) */
if (!(args = PyTuple_New(0))) { goto exit; } if (!(args = PyTuple_New(0))) { goto exit; }

View File

@ -26,7 +26,7 @@
import sys import sys
import math import math
import pickle import pickle
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta, timezone
import psycopg2 import psycopg2
from psycopg2.tz import FixedOffsetTimezone, ZERO from psycopg2.tz import FixedOffsetTimezone, ZERO
@ -192,6 +192,22 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
value_utc = value.astimezone(UTC).replace(tzinfo=None) value_utc = value.astimezone(UTC).replace(tzinfo=None)
self.assertEqual(base - value_utc, timedelta(seconds=offset)) self.assertEqual(base - value_utc, timedelta(seconds=offset))
def test_default_tzinfo(self):
self.curs.execute("select '2000-01-01 00:00+02:00'::timestamptz")
dt = self.curs.fetchone()[0]
self.assert_(isinstance(dt.tzinfo, timezone))
self.assertEqual(dt,
datetime(2000, 1, 1, tzinfo=timezone(timedelta(minutes=120))))
def test_fotz_tzinfo(self):
self.curs.tzinfo_factory = FixedOffsetTimezone
self.curs.execute("select '2000-01-01 00:00+02:00'::timestamptz")
dt = self.curs.fetchone()[0]
self.assert_(not isinstance(dt.tzinfo, timezone))
self.assert_(isinstance(dt.tzinfo, FixedOffsetTimezone))
self.assertEqual(dt,
datetime(2000, 1, 1, tzinfo=timezone(timedelta(minutes=120))))
def test_parse_datetime_timezone(self): def test_parse_datetime_timezone(self):
self.check_datetime_tz("+01", 3600) self.check_datetime_tz("+01", 3600)
self.check_datetime_tz("-01", -3600) self.check_datetime_tz("-01", -3600)