From 7254587838211258dfc71e466556f951f40467e0 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 16 Jun 2017 20:50:28 +0100 Subject: [PATCH] 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 --- NEWS | 4 ++++ psycopg/typecast_datetime.c | 3 +++ tests/test_dates.py | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/NEWS b/NEWS index 0a5de096..3437775b 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,10 @@ What's new in psycopg 2.7.2 - Parse intervals returned as microseconds from Redshift (:ticket:`#558`). - Added `~psycopg2.extras.Json` `!prepare()` method to consider connection 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. diff --git a/psycopg/typecast_datetime.c b/psycopg/typecast_datetime.c index b0b257bc..279a87dd 100644 --- a/psycopg/typecast_datetime.c +++ b/psycopg/typecast_datetime.c @@ -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 */ 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", days, seconds, micros); } diff --git a/tests/test_dates.py b/tests/test_dates.py index a5415586..d556d78c 100755 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -223,6 +223,32 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin): def test_parse_negative_interval(self): 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): value = self.DATETIME('-infinity', self.curs) self.assertEqual(str(value), '0001-01-01 00:00:00')