]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix[c]: fix interval parsing with day/month/year and negative time 1072/head
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 7 May 2025 14:01:01 +0000 (16:01 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 7 May 2025 14:01:01 +0000 (16:01 +0200)
Close #1071

docs/news.rst
psycopg_c/psycopg_c/types/datetime.pyx
tests/types/test_datetime.py

index a6bf02ae706652caf3643ccb2c76d8c74efbd390..1238607fa44ff0241d22fc88d2b280662c909bb4 100644 (file)
@@ -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
index a14456b098286c7b4cdf4cc6e71446b5757d7908..3e9719f2d8b4930b57242fbb90737cc5a11e7816 100644 (file)
@@ -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)
index 19c84373ccf67f286d81f0c9e4be10a953e4a13f..42454374159ef8a271fc5b43188cd3264643007e 100644 (file)
@@ -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)