summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>2017-02-24 03:48:41 +0100
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>2017-02-24 03:48:41 +0100
commit834e9996dac55b230d26bc5b3a3d7c56e9bf92e2 (patch)
tree764742ebb4eba01d4967dfea51f121841ca24c1b
parente351606b692439ad99a57d4c21c16f29b14f11c3 (diff)
downloadpsycopg2-834e9996dac55b230d26bc5b3a3d7c56e9bf92e2.tar.gz
Parse interval only using integers
(almost... except for micros rounding) While this is probably an improvement on the previous implementation, I am largely waving a dead chicken at windows, which keeps failing to pass the seconds overflow test. If it doesn't pass now either I'll start blaming Python's timedelta.
-rw-r--r--psycopg/typecast_datetime.c67
-rwxr-xr-xtests/test_dates.py25
2 files changed, 57 insertions, 35 deletions
diff --git a/psycopg/typecast_datetime.c b/psycopg/typecast_datetime.c
index 6f06308..fe99714 100644
--- a/psycopg/typecast_datetime.c
+++ b/psycopg/typecast_datetime.c
@@ -220,11 +220,10 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
static PyObject *
typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
{
- long years = 0, months = 0, days = 0, lsec;
- double hours = 0.0, minutes = 0.0, seconds = 0.0, hundredths = 0.0;
- double v = 0.0, sign = 1.0, denominator = 1.0;
+ long years = 0, months = 0, days = 0;
+ long hours = 0, minutes = 0, seconds = 0, micros = 0;
+ long v = 0, sign = 1, denom = 1;
int part = 0;
- double micro;
if (str == NULL) { Py_RETURN_NONE; }
@@ -234,56 +233,56 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
switch (*str) {
case '-':
- sign = -1.0;
+ sign = -1;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
- v = v * 10.0 + (double)(*str - '0');
+ v = v * 10 + (*str - '0');
if (part == 6){
- denominator *= 10;
+ denom *= 10;
}
break;
case 'y':
if (part == 0) {
- years = (long)(v*sign);
+ years = v * sign;
+ v = 0; sign = 1; part = 1;
str = skip_until_space2(str, &len);
- v = 0.0; sign = 1.0; part = 1;
}
break;
case 'm':
if (part <= 1) {
- months = (long)(v*sign);
+ months = v * sign;
+ v = 0; sign = 1; part = 2;
str = skip_until_space2(str, &len);
- v = 0.0; sign = 1.0; part = 2;
}
break;
case 'd':
if (part <= 2) {
- days = (long)(v*sign);
+ days = v * sign;
+ v = 0; sign = 1; part = 3;
str = skip_until_space2(str, &len);
- v = 0.0; sign = 1.0; part = 3;
}
break;
case ':':
if (part <= 3) {
hours = v;
- v = 0.0; part = 4;
+ v = 0; part = 4;
}
else if (part == 4) {
minutes = v;
- v = 0.0; part = 5;
+ v = 0; part = 5;
}
break;
case '.':
if (part == 5) {
seconds = v;
- v = 0.0; part = 6;
+ v = 0; part = 6;
}
break;
@@ -294,7 +293,7 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
str++;
}
- /* manage last value, be it minutes or seconds or hundredths of a second */
+ /* manage last value, be it minutes or seconds or microseconds */
if (part == 4) {
minutes = v;
}
@@ -302,25 +301,31 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
seconds = v;
}
else if (part == 6) {
- hundredths = v;
- hundredths = hundredths/denominator;
+ micros = v;
+ if (denom < 1000000L) {
+ do {
+ micros *= 10;
+ denom *= 10;
+ } while (denom < 1000000L);
+ }
+ else if (denom > 1000000L) {
+ micros = (long)((double)micros * 1000000.0 / denom);
+ }
}
- /* calculates seconds */
- if (sign < 0.0) {
- seconds = - (hundredths + seconds + minutes*60 + hours*3600);
- }
- else {
- seconds += hundredths + minutes*60 + hours*3600;
+ /* add hour, minutes, seconds, and include the sign */
+ seconds += 60 * minutes + 3600 * hours;
+
+ if (sign < 0) {
+ seconds = -seconds;
+ micros = -micros;
}
- /* calculates days */
- days += years*365 + months*30;
+ /* add the days - these items already included their own sign */
+ seconds += (3600 * 24) * (days + 30 * months + 365 * years);
- lsec = (long)seconds;
- micro = (seconds - (double)lsec) * 1000000.0;
- return PyObject_CallFunction((PyObject*)PyDateTimeAPI->DeltaType, "lli",
- days, lsec, (int)round(micro));
+ return PyObject_CallFunction((PyObject*)PyDateTimeAPI->DeltaType, "lll",
+ 0L, seconds, micros);
}
/* psycopg defaults to using python datetime types */
diff --git a/tests/test_dates.py b/tests/test_dates.py
index 1000997..bffd05d 100755
--- a/tests/test_dates.py
+++ b/tests/test_dates.py
@@ -28,6 +28,11 @@ from psycopg2.tz import FixedOffsetTimezone, ZERO
from testutils import unittest, ConnectingTestCase, skip_before_postgres
+def total_seconds(d):
+ """Return total number of seconds of a timedelta as a float."""
+ return d.days * 24 * 60 * 60 + d.seconds + d.microseconds / 1000000.0
+
+
class CommonDatetimeTestsMixin:
def execute(self, *args):
@@ -334,10 +339,6 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
self.assertEqual(t, time(0, 0, tzinfo=FixedOffsetTimezone(330)))
def test_large_interval(self):
- def total_seconds(d):
- """Return total number of seconds of a timedelta as a float."""
- return d.days * 24 * 60 * 60 + d.seconds + d.microseconds / 1000000.0
-
t = self.execute("select '999999:00:00'::interval")
self.assertEqual(total_seconds(t), 999999 * 60 * 60)
@@ -356,6 +357,22 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
t = self.execute("select '-999999:00:00.9'::interval")
self.assertEqual(total_seconds(t), -999999 * 60 * 60 - 0.9)
+ def test_micros_rounding(self):
+ t = self.execute("select '0.1'::interval")
+ self.assertEqual(total_seconds(t), 0.1)
+
+ t = self.execute("select '0.01'::interval")
+ self.assertEqual(total_seconds(t), 0.01)
+
+ t = self.execute("select '0.000001'::interval")
+ self.assertEqual(total_seconds(t), 1e-6)
+
+ t = self.execute("select '0.0000004'::interval")
+ self.assertEqual(total_seconds(t), 0)
+
+ t = self.execute("select '0.0000006'::interval")
+ self.assertEqual(total_seconds(t), 1e-6)
+
# Only run the datetime tests if psycopg was compiled with support.
if not hasattr(psycopg2.extensions, 'PYDATETIME'):