Merge remote-tracking branch 'origin/fix-536'

This commit is contained in:
Daniele Varrazzo 2017-03-22 12:19:31 +00:00
commit adf55babe8
9 changed files with 171 additions and 71 deletions

2
NEWS
View File

@ -9,6 +9,8 @@ What's new in psycopg 2.7.2
2.7 by mistake.
- Don't display the password in `connection.dsn` when the connection
string is specified as an URI (:ticket:`#528`).
- Return objects with timezone parsing "infinity" :sql:`timestamptz`
(:ticket:`#536`).
What's new in psycopg 2.7.1

View File

@ -823,10 +823,12 @@ from the database. See :ref:`unicode-handling` for details.
.. data:: PYDATE
PYDATETIME
PYDATETIMETZ
PYINTERVAL
PYTIME
PYDATEARRAY
PYDATETIMEARRAY
PYDATETIMETZARRAY
PYINTERVALARRAY
PYTIMEARRAY
@ -835,10 +837,12 @@ from the database. See :ref:`unicode-handling` for details.
.. data:: MXDATE
MXDATETIME
MXDATETIMETZ
MXINTERVAL
MXTIME
MXDATEARRAY
MXDATETIMEARRAY
MXDATETIMETZARRAY
MXINTERVALARRAY
MXTIMEARRAY
@ -851,3 +855,5 @@ from the database. See :ref:`unicode-handling` for details.
module. In older versions they can be imported from the implementation
module `!psycopg2._psycopg`.
.. versionchanged:: 2.7.2
added `!*DATETIMETZ*` objects.

View File

@ -43,16 +43,16 @@ from psycopg2._psycopg import ( # noqa
try:
from psycopg2._psycopg import ( # noqa
MXDATE, MXDATETIME, MXINTERVAL, MXTIME,
MXDATEARRAY, MXDATETIMEARRAY, MXINTERVALARRAY, MXTIMEARRAY,
MXDATE, MXDATETIME, MXDATETIMETZ, MXINTERVAL, MXTIME, MXDATEARRAY,
MXDATETIMEARRAY, MXDATETIMETZARRAY, MXINTERVALARRAY, MXTIMEARRAY,
DateFromMx, TimeFromMx, TimestampFromMx, IntervalFromMx, )
except ImportError:
pass
try:
from psycopg2._psycopg import ( # noqa
PYDATE, PYDATETIME, PYINTERVAL, PYTIME,
PYDATEARRAY, PYDATETIMEARRAY, PYINTERVALARRAY, PYTIMEARRAY,
PYDATE, PYDATETIME, PYDATETIMETZ, PYINTERVAL, PYTIME, PYDATEARRAY,
PYDATETIMEARRAY, PYDATETIMETZARRAY, PYINTERVALARRAY, PYTIMEARRAY,
DateFromPy, TimeFromPy, TimestampFromPy, IntervalFromPy, )
except ImportError:
pass

View File

@ -197,6 +197,7 @@ typecast_UNKNOWN_cast(const char *str, Py_ssize_t len, PyObject *curs)
#include "psycopg/typecast_builtins.c"
#define typecast_PYDATETIMEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_PYDATETIMETZARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_PYDATEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_PYTIMEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_PYINTERVALARRAY_cast typecast_GENERIC_ARRAY_cast
@ -204,10 +205,12 @@ typecast_UNKNOWN_cast(const char *str, Py_ssize_t len, PyObject *curs)
/* a list of initializers, used to make the typecasters accessible anyway */
static typecastObject_initlist typecast_pydatetime[] = {
{"PYDATETIME", typecast_DATETIME_types, typecast_PYDATETIME_cast},
{"PYDATETIMETZ", typecast_DATETIMETZ_types, typecast_PYDATETIMETZ_cast},
{"PYTIME", typecast_TIME_types, typecast_PYTIME_cast},
{"PYDATE", typecast_DATE_types, typecast_PYDATE_cast},
{"PYINTERVAL", typecast_INTERVAL_types, typecast_PYINTERVAL_cast},
{"PYDATETIMEARRAY", typecast_DATETIMEARRAY_types, typecast_PYDATETIMEARRAY_cast, "PYDATETIME"},
{"PYDATETIMETZARRAY", typecast_DATETIMETZARRAY_types, typecast_PYDATETIMETZARRAY_cast, "PYDATETIMETZ"},
{"PYTIMEARRAY", typecast_TIMEARRAY_types, typecast_PYTIMEARRAY_cast, "PYTIME"},
{"PYDATEARRAY", typecast_DATEARRAY_types, typecast_PYDATEARRAY_cast, "PYDATE"},
{"PYINTERVALARRAY", typecast_INTERVALARRAY_types, typecast_PYINTERVALARRAY_cast, "PYINTERVAL"},
@ -216,6 +219,7 @@ static typecastObject_initlist typecast_pydatetime[] = {
#ifdef HAVE_MXDATETIME
#define typecast_MXDATETIMEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_MXDATETIMETZARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_MXDATEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_MXTIMEARRAY_cast typecast_GENERIC_ARRAY_cast
#define typecast_MXINTERVALARRAY_cast typecast_GENERIC_ARRAY_cast
@ -223,10 +227,12 @@ static typecastObject_initlist typecast_pydatetime[] = {
/* a list of initializers, used to make the typecasters accessible anyway */
static typecastObject_initlist typecast_mxdatetime[] = {
{"MXDATETIME", typecast_DATETIME_types, typecast_MXDATE_cast},
{"MXDATETIMETZ", typecast_DATETIMETZ_types, typecast_MXDATE_cast},
{"MXTIME", typecast_TIME_types, typecast_MXTIME_cast},
{"MXDATE", typecast_DATE_types, typecast_MXDATE_cast},
{"MXINTERVAL", typecast_INTERVAL_types, typecast_MXINTERVAL_cast},
{"MXDATETIMEARRAY", typecast_DATETIMEARRAY_types, typecast_MXDATETIMEARRAY_cast, "MXDATETIME"},
{"MXDATETIMETZARRAY", typecast_DATETIMETZARRAY_types, typecast_MXDATETIMETZARRAY_cast, "MXDATETIMETZ"},
{"MXTIMEARRAY", typecast_TIMEARRAY_types, typecast_MXTIMEARRAY_cast, "MXTIME"},
{"MXDATEARRAY", typecast_DATEARRAY_types, typecast_MXDATEARRAY_cast, "MXDATE"},
{"MXINTERVALARRAY", typecast_INTERVALARRAY_types, typecast_MXINTERVALARRAY_cast, "MXINTERVAL"},

View File

@ -7,6 +7,7 @@ static long int typecast_UNICODE_types[] = {19, 18, 25, 1042, 1043, 0};
static long int typecast_STRING_types[] = {19, 18, 25, 1042, 1043, 0};
static long int typecast_BOOLEAN_types[] = {16, 0};
static long int typecast_DATETIME_types[] = {1114, 1184, 704, 1186, 0};
static long int typecast_DATETIMETZ_types[] = {1184, 0};
static long int typecast_TIME_types[] = {1083, 1266, 0};
static long int typecast_DATE_types[] = {1082, 0};
static long int typecast_INTERVAL_types[] = {704, 1186, 0};
@ -20,6 +21,7 @@ static long int typecast_UNICODEARRAY_types[] = {1002, 1003, 1009, 1014, 1015, 0
static long int typecast_STRINGARRAY_types[] = {1002, 1003, 1009, 1014, 1015, 0};
static long int typecast_BOOLEANARRAY_types[] = {1000, 0};
static long int typecast_DATETIMEARRAY_types[] = {1115, 1185, 0};
static long int typecast_DATETIMETZARRAY_types[] = {1185, 0};
static long int typecast_TIMEARRAY_types[] = {1183, 1270, 0};
static long int typecast_DATEARRAY_types[] = {1182, 0};
static long int typecast_INTERVALARRAY_types[] = {1187, 0};
@ -41,6 +43,7 @@ static typecastObject_initlist typecast_builtins[] = {
{"STRING", typecast_STRING_types, typecast_STRING_cast, NULL},
{"BOOLEAN", typecast_BOOLEAN_types, typecast_BOOLEAN_cast, NULL},
{"DATETIME", typecast_DATETIME_types, typecast_DATETIME_cast, NULL},
{"DATETIMETZ", typecast_DATETIMETZ_types, typecast_DATETIMETZ_cast, NULL},
{"TIME", typecast_TIME_types, typecast_TIME_cast, NULL},
{"DATE", typecast_DATE_types, typecast_DATE_cast, NULL},
{"INTERVAL", typecast_INTERVAL_types, typecast_INTERVAL_cast, NULL},

View File

@ -80,91 +80,152 @@ typecast_PYDATE_cast(const char *str, Py_ssize_t len, PyObject *curs)
return obj;
}
/** DATETIME - cast a timestamp into a datetime python object **/
/* convert the strings -infinity and infinity into a datetime with timezone */
static PyObject *
_parse_inftz(const char *str, PyObject *curs)
{
PyObject *rv = NULL;
PyObject *m = NULL;
PyObject *tzinfo_factory = NULL;
PyObject *tzinfo = NULL;
PyObject *args = NULL;
PyObject *kwargs = NULL;
PyObject *replace = NULL;
if (!(m = PyObject_GetAttrString(
(PyObject*)PyDateTimeAPI->DateTimeType,
(str[0] == '-' ? "min" : "max")))) {
goto exit;
}
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
if (tzinfo_factory == Py_None) {
rv = m;
m = NULL;
goto exit;
}
if (!(tzinfo = PyObject_CallFunction(tzinfo_factory, "i", 0))) {
goto exit;
}
/* m.replace(tzinfo=tzinfo) */
if (!(args = PyTuple_New(0))) { goto exit; }
if (!(kwargs = PyDict_New())) { goto exit; }
if (0 != PyDict_SetItemString(kwargs, "tzinfo", tzinfo)) { goto exit; }
if (!(replace = PyObject_GetAttrString(m, "replace"))) { goto exit; }
rv = PyObject_Call(replace, args, kwargs);
exit:
Py_XDECREF(replace);
Py_XDECREF(args);
Py_XDECREF(kwargs);
Py_XDECREF(tzinfo);
Py_XDECREF(m);
return rv;
}
static PyObject *
typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
_parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
{
PyObject* obj = NULL;
PyObject* rv = NULL;
PyObject *tzinfo = NULL;
PyObject *tzinfo_factory;
int n, y=0, m=0, d=0;
int hh=0, mm=0, ss=0, us=0, tz=0;
const char *tp = NULL;
Dprintf("typecast_PYDATETIMETZ_cast: s = %s", str);
n = typecast_parse_date(str, &tp, &len, &y, &m, &d);
Dprintf("typecast_PYDATE_cast: tp = %p "
"n = %d, len = " FORMAT_CODE_PY_SSIZE_T ","
" y = %d, m = %d, d = %d",
tp, n, len, y, m, d);
if (n != 3) {
PyErr_SetString(DataError, "unable to parse date");
goto exit;
}
if (len > 0) {
n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tz);
Dprintf("typecast_PYDATETIMETZ_cast: n = %d,"
" len = " FORMAT_CODE_PY_SSIZE_T ","
" hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
n, len, hh, mm, ss, us, tz);
if (n < 3 || n > 6) {
PyErr_SetString(DataError, "unable to parse time");
goto exit;
}
}
if (ss > 59) {
mm += 1;
ss -= 60;
}
if (y > 9999)
y = 9999;
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
if (n >= 5 && tzinfo_factory != Py_None) {
/* we have a time zone, calculate minutes and create
appropriate tzinfo object calling the factory */
Dprintf("typecast_PYDATETIMETZ_cast: UTC offset = %ds", tz);
/* The datetime module requires that time zone offsets be
a whole number of minutes, so truncate the seconds to the
closest minute. */
// printf("%d %d %d\n", tz, tzmin, round(tz / 60.0));
if (!(tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
(int)round(tz / 60.0)))) {
goto exit;
}
} else {
Py_INCREF(Py_None);
tzinfo = Py_None;
}
Dprintf("typecast_PYDATETIMETZ_cast: tzinfo: %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
tzinfo, Py_REFCNT(tzinfo));
rv = PyObject_CallFunction(
(PyObject*)PyDateTimeAPI->DateTimeType, "iiiiiiiO",
y, m, d, hh, mm, ss, us, tzinfo);
exit:
Py_XDECREF(tzinfo);
return rv;
}
/** DATETIME - cast a timestamp into a datetime python object **/
static PyObject *
typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
if (str == NULL) { Py_RETURN_NONE; }
/* check for infinity */
if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) {
if (str[0] == '-') {
obj = PyObject_GetAttrString(
(PyObject*)PyDateTimeAPI->DateTimeType, "min");
}
else {
obj = PyObject_GetAttrString(
(PyObject*)PyDateTimeAPI->DateTimeType, "max");
}
return PyObject_GetAttrString(
(PyObject*)PyDateTimeAPI->DateTimeType,
(str[0] == '-' ? "min" : "max"));
}
else {
Dprintf("typecast_PYDATETIME_cast: s = %s", str);
n = typecast_parse_date(str, &tp, &len, &y, &m, &d);
Dprintf("typecast_PYDATE_cast: tp = %p "
"n = %d, len = " FORMAT_CODE_PY_SSIZE_T ","
" y = %d, m = %d, d = %d",
tp, n, len, y, m, d);
if (n != 3) {
PyErr_SetString(DataError, "unable to parse date");
return NULL;
}
return _parse_noninftz(str, len, curs);
}
if (len > 0) {
n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tz);
Dprintf("typecast_PYDATETIME_cast: n = %d,"
" len = " FORMAT_CODE_PY_SSIZE_T ","
" hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
n, len, hh, mm, ss, us, tz);
if (n < 3 || n > 6) {
PyErr_SetString(DataError, "unable to parse time");
return NULL;
}
}
/** DATETIMETZ - cast a timestamptz into a datetime python object **/
if (ss > 59) {
mm += 1;
ss -= 60;
}
if (y > 9999)
y = 9999;
static PyObject *
typecast_PYDATETIMETZ_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
if (str == NULL) { Py_RETURN_NONE; }
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
if (n >= 5 && tzinfo_factory != Py_None) {
/* we have a time zone, calculate minutes and create
appropriate tzinfo object calling the factory */
Dprintf("typecast_PYDATETIME_cast: UTC offset = %ds", tz);
/* The datetime module requires that time zone offsets be
a whole number of minutes, so truncate the seconds to the
closest minute. */
// printf("%d %d %d\n", tz, tzmin, round(tz / 60.0));
tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
(int)round(tz / 60.0));
} else {
Py_INCREF(Py_None);
tzinfo = Py_None;
}
if (tzinfo != NULL) {
obj = PyObject_CallFunction(
(PyObject*)PyDateTimeAPI->DateTimeType, "iiiiiiiO",
y, m, d, hh, mm, ss, us, tzinfo);
Dprintf("typecast_PYDATETIME_cast: tzinfo: %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
tzinfo, Py_REFCNT(tzinfo)
);
Py_DECREF(tzinfo);
}
if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) {
return _parse_inftz(str, curs);
}
return obj;
return _parse_noninftz(str, len, curs);
}
/** TIME - parse time into a time object **/
@ -345,4 +406,5 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
#define typecast_TIME_cast typecast_PYTIME_cast
#define typecast_INTERVAL_cast typecast_PYINTERVAL_cast
#define typecast_DATETIME_cast typecast_PYDATETIME_cast
#define typecast_DATETIMETZ_cast typecast_PYDATETIMETZ_cast
#endif

View File

@ -248,5 +248,6 @@ typecast_MXINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
#define typecast_TIME_cast typecast_MXTIME_cast
#define typecast_INTERVAL_cast typecast_MXINTERVAL_cast
#define typecast_DATETIME_cast typecast_MXDATE_cast
#define typecast_DATETIMETZ_cast typecast_MXDATE_cast
#endif

View File

@ -528,9 +528,10 @@ have_mxdatetime = False
use_pydatetime = int(parser.get('build_ext', 'use_pydatetime'))
# check for mx package
mxincludedir = ''
if parser.has_option('build_ext', 'mx_include_dir'):
mxincludedir = parser.get('build_ext', 'mx_include_dir')
else:
if not mxincludedir:
mxincludedir = os.path.join(get_python_inc(plat_specific=1), "mx")
if mxincludedir.strip() and os.path.exists(mxincludedir):
# Build the support for mx: we will check at runtime if it can be imported

View File

@ -392,6 +392,25 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
self.assertRaises(OverflowError, f, '00:00:100000000000000000:00')
self.assertRaises(OverflowError, f, '00:00:00.100000000000000000')
def test_adapt_infinity_tz(self):
from datetime import datetime
t = self.execute("select 'infinity'::timestamp")
self.assert_(t.tzinfo is None)
self.assert_(t > datetime(4000, 1, 1))
t = self.execute("select '-infinity'::timestamp")
self.assert_(t.tzinfo is None)
self.assert_(t < datetime(1000, 1, 1))
t = self.execute("select 'infinity'::timestamptz")
self.assert_(t.tzinfo is not None)
self.assert_(t > datetime(4000, 1, 1, tzinfo=FixedOffsetTimezone()))
t = self.execute("select '-infinity'::timestamptz")
self.assert_(t.tzinfo is not None)
self.assert_(t < datetime(1000, 1, 1, tzinfo=FixedOffsetTimezone()))
# Only run the datetime tests if psycopg was compiled with support.
if not hasattr(psycopg2.extensions, 'PYDATETIME'):