diff options
author | Daniele Varrazzo <daniele.varrazzo@gmail.com> | 2017-02-24 03:48:41 +0100 |
---|---|---|
committer | Daniele Varrazzo <daniele.varrazzo@gmail.com> | 2017-02-24 03:48:41 +0100 |
commit | 834e9996dac55b230d26bc5b3a3d7c56e9bf92e2 (patch) | |
tree | 764742ebb4eba01d4967dfea51f121841ca24c1b | |
parent | e351606b692439ad99a57d4c21c16f29b14f11c3 (diff) | |
download | psycopg2-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.c | 67 | ||||
-rwxr-xr-x | tests/test_dates.py | 25 |
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'): |