summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--psycopg/typecast_datetime.c53
-rwxr-xr-xtests/test_dates.py19
2 files changed, 72 insertions, 0 deletions
diff --git a/psycopg/typecast_datetime.c b/psycopg/typecast_datetime.c
index fec57de..b0b257b 100644
--- a/psycopg/typecast_datetime.c
+++ b/psycopg/typecast_datetime.c
@@ -276,6 +276,44 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
return obj;
}
+
+/* Attempt parsing a number as microseconds
+ * Redshift is reported returning this stuff, see #558
+ *
+ * Return a new `timedelta()` object in case of success or NULL and set an error
+ */
+static PyObject *
+interval_from_usecs(const char *str)
+{
+ PyObject *us = NULL;
+ char *pend;
+ PyObject *rv = NULL;
+
+ Dprintf("interval_from_usecs: %s", str);
+
+ if (!(us = PyLong_FromString((char *)str, &pend, 0))) {
+ Dprintf("interval_from_usecs: parsing long failed");
+ goto exit;
+ }
+
+ if (*pend != '\0') {
+ /* there are trailing chars, it's not just micros. Barf. */
+ Dprintf("interval_from_usecs: spurious chars %s", pend);
+ PyErr_Format(PyExc_ValueError,
+ "expected number of microseconds, got %s", str);
+ goto exit;
+ }
+
+ rv = PyObject_CallFunction(
+ (PyObject*)PyDateTimeAPI->DeltaType, "LLO",
+ 0L, 0L, us);
+
+exit:
+ Py_XDECREF(us);
+ return rv;
+}
+
+
/** INTERVAL - parse an interval into a timedelta object **/
static PyObject *
@@ -284,6 +322,7 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
long v = 0, years = 0, months = 0, hours = 0, minutes = 0, micros = 0;
PY_LONG_LONG days = 0, seconds = 0;
int sign = 1, denom = 1, part = 0;
+ const char *orig = str;
if (str == NULL) { Py_RETURN_NONE; }
@@ -305,6 +344,16 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
* or too big value. On Win where long == int the 2nd check
* is useless. */
if (v1 < v || v1 > (long)INT_MAX) {
+ /* uhm, oops... but before giving up, maybe it's redshift
+ * returning microseconds? See #558 */
+ PyObject *rv;
+ if ((rv = interval_from_usecs(orig))) {
+ return rv;
+ }
+ else {
+ PyErr_Clear();
+ }
+
PyErr_SetString(
PyExc_OverflowError, "interval component too big");
return NULL;
@@ -384,6 +433,10 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
micros = (long)round((double)micros / denom * 1000000.0);
}
}
+ else if (part == 0) {
+ /* Parsing failed, maybe it's just an integer? Assume usecs */
+ return interval_from_usecs(orig);
+ }
/* add hour, minutes, seconds together and include the sign */
seconds += 60 * (PY_LONG_LONG)minutes + 3600 * (PY_LONG_LONG)hours;
diff --git a/tests/test_dates.py b/tests/test_dates.py
index 83eea32..f2f49f2 100755
--- a/tests/test_dates.py
+++ b/tests/test_dates.py
@@ -411,6 +411,25 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
self.assert_(t.tzinfo is not None)
self.assert_(t < datetime(1000, 1, 1, tzinfo=FixedOffsetTimezone()))
+ def test_redshift_day(self):
+ # Redshift is reported returning 1 day interval as microsec (bug #558)
+ cur = self.conn.cursor()
+ psycopg2.extensions.register_type(
+ psycopg2.extensions.new_type(
+ psycopg2.STRING.values, 'WAT', psycopg2.extensions.INTERVAL),
+ cur)
+
+ from datetime import timedelta
+ for s, v in [
+ ('0', timedelta(0)),
+ ('1000000', timedelta(seconds=1)), # Is this a thing?
+ ('86400000000', timedelta(days=1)),
+ ('-86400000000', timedelta(days=-1)),
+ ]:
+ cur.execute("select %s::text", (s,))
+ r = cur.fetchone()[0]
+ self.assertEqual(r, v, "%s -> %s != %s" % (s, r, v))
+
# Only run the datetime tests if psycopg was compiled with support.
if not hasattr(psycopg2.extensions, 'PYDATETIME'):