Add 6 hours per year converting from interval

This is the same operation performed by postgres when extracting the
number of seconds of an interval:

    =# select extract(epoch from '1 year'::interval) / 60 / 60 / 24;
     ?column?
    ----------
       365.25

This way `extract(epoch from interval)` will match the resulting
`timedelta.total_seconds()`.

Close #570
This commit is contained in:
Daniele Varrazzo 2017-06-16 20:50:28 +01:00
parent 45f7ec73be
commit 7254587838
3 changed files with 33 additions and 0 deletions

4
NEWS
View File

@ -24,6 +24,10 @@ What's new in psycopg 2.7.2
- Parse intervals returned as microseconds from Redshift (:ticket:`#558`). - Parse intervals returned as microseconds from Redshift (:ticket:`#558`).
- Added `~psycopg2.extras.Json` `!prepare()` method to consider connection - Added `~psycopg2.extras.Json` `!prepare()` method to consider connection
params when adapting (:ticket:`#562`). params when adapting (:ticket:`#562`).
- Convert PostgreSQL :sql:`interval` containing years and months into
Python `~datetime.timedelta` with `!total_seconds()` matching the
:sql:`interval` epoch, i.e. add 6 hours every year or 12 months
(:ticket:`#570`).
- `~psycopg2.errorcodes` map updated to PostgreSQL 10 beta 1. - `~psycopg2.errorcodes` map updated to PostgreSQL 10 beta 1.

View File

@ -448,6 +448,9 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
/* add the days, months years together - they already include a sign */ /* add the days, months years together - they already include a sign */
days += 30 * (PY_LONG_LONG)months + 365 * (PY_LONG_LONG)years; days += 30 * (PY_LONG_LONG)months + 365 * (PY_LONG_LONG)years;
/* Postgres adds 6 hours for each year, or for each 12 months */
seconds += 6 * 60 * 60 * (PY_LONG_LONG)(years + (months / 12));
return PyObject_CallFunction((PyObject*)PyDateTimeAPI->DeltaType, "LLl", return PyObject_CallFunction((PyObject*)PyDateTimeAPI->DeltaType, "LLl",
days, seconds, micros); days, seconds, micros);
} }

View File

@ -223,6 +223,32 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
def test_parse_negative_interval(self): def test_parse_negative_interval(self):
self._check_interval('-42 days -12:34:56.123456') self._check_interval('-42 days -12:34:56.123456')
def test_parse_mixied_signs_interval(self):
# Intervals in postgres have 4 blocks
# -1 year -2 mons +3 days -04:05:06
# each can be missing, present with no sign, positive, negative
# test all the combos
def cases(s):
return ['', s, '-' + s, '+' + s]
from itertools import product
for parts in product(
cases('1 year'), cases('2 months'),
cases('3 days'), cases('04:05:06')):
s = ' '.join(parts)
if s.isspace():
continue
self._check_interval(s)
def test_parse_interval_element(self):
for i in range(-1000, 1100, 100):
self._check_interval('%s years' % i)
self._check_interval('%s months' % i)
self._check_interval('%s days' % i)
self._check_interval('%s hours' % i)
self._check_interval('%s minutes' % i)
self._check_interval('%s seconds' % i)
def test_parse_infinity(self): def test_parse_infinity(self):
value = self.DATETIME('-infinity', self.curs) value = self.DATETIME('-infinity', self.curs)
self.assertEqual(str(value), '0001-01-01 00:00:00') self.assertEqual(str(value), '0001-01-01 00:00:00')