From: Daniele Varrazzo Date: Wed, 7 May 2025 14:01:01 +0000 (+0200) Subject: fix[c]: fix interval parsing with day/month/year and negative time X-Git-Tag: 3.2.8~20^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bdf98de5e6fb1d627264092347edaf5158d9869a;p=thirdparty%2Fpsycopg.git fix[c]: fix interval parsing with day/month/year and negative time Close #1071 --- diff --git a/docs/news.rst b/docs/news.rst index a6bf02ae7..1238607fa 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -19,6 +19,8 @@ Psycopg 3.2.8 (unreleased) the local timezone (:ticket:`#1058`). - Fix `~Cursor.rownumber` after using `~AsyncServerCursor.scroll()` on `AsyncServerCursor` (:ticket:`#1066`). +- Fix interval parsing with days or other parts and negative time in C module + (:ticket:`#1071`). Current release diff --git a/psycopg_c/psycopg_c/types/datetime.pyx b/psycopg_c/psycopg_c/types/datetime.pyx index a14456b09..3e9719f2d 100644 --- a/psycopg_c/psycopg_c/types/datetime.pyx +++ b/psycopg_c/psycopg_c/types/datetime.pyx @@ -891,18 +891,18 @@ cdef class IntervalLoader(CLoader): cdef const char *sep cdef const char *end = ptr + length - # If there are spaces, there is a [+|-]n [days|months|years] while True: + # If there are spaces, there is a [+|-]n [days|months|years] + sep = strchr(ptr, b' ') + if sep == NULL or sep > end: + break + if ptr[0] == b'-' or ptr[0] == b'+': sign = ptr[0] ptr += 1 else: sign = 0 - sep = strchr(ptr, b' ') - if sep == NULL or sep > end: - break - val = 0 ptr = _parse_date_values(ptr, end, &val, 1) if ptr == NULL: @@ -929,10 +929,18 @@ cdef class IntervalLoader(CLoader): else: break - # Parse the time part. An eventual sign was already consumed in the loop + # Parse the time part if present cdef int64_t vals[3] - memset(vals, 0, sizeof(vals)) if ptr != NULL: + memset(vals, 0, sizeof(vals)) + + # Parse the sign of the time part. + if ptr[0] == b'-' or ptr[0] == b'+': + sign = ptr[0] + ptr += 1 + else: + sign = 0 + ptr = _parse_date_values(ptr, end, vals, ARRAYSIZE(vals)) if ptr == NULL: s = bytes(data).decode("utf8", "replace") @@ -941,15 +949,18 @@ cdef class IntervalLoader(CLoader): secs = vals[2] + 60 * (vals[1] + 60 * vals[0]) if secs > 86_400: - days += secs // 86_400 + if sign == b'-': + days -= secs // 86_400 + else: + days += secs // 86_400 secs %= 86_400 if ptr[0] == b'.': ptr = _parse_micros(ptr + 1, &us) - if sign == b'-': - secs = -secs - us = -us + if sign == b'-': + secs = -secs + us = -us try: return cdt.timedelta_new(days, secs, us) diff --git a/tests/types/test_datetime.py b/tests/types/test_datetime.py index 19c84373c..424543741 100644 --- a/tests/types/test_datetime.py +++ b/tests/types/test_datetime.py @@ -713,8 +713,16 @@ class TestInterval: ("60d", "2 month"), ("-90d", "-3 month"), ("186d", "6 mons 6 days"), + ("174d", "6 mons -6 days"), ("736d", "2 years 6 days"), + ("724d", "2 years -6 days"), + ("330d", "1 years -1 month"), ("83063d,81640s,447000m", "1993534:40:40.447"), + ("-1d,64800s", "41 days -990:00:00"), + ("0s", "10 days -240:00:00"), + ("20d", "10 days 240:00:00"), + ("0d", "-10 days 240:00:00"), + ("-20d", "-10 days -240:00:00"), ], ) @pytest.mark.parametrize("fmt_out", pq.Format)