Skip to content

Commit cceaa73

Browse files
committed
* psycopg/typecast.c (typecast_parse_time): give the correct
return value for partially parsed time values. * psycopg/typecast_mxdatetime.c (typecast_MXDATE_cast): return NULL after setting DataError. Also, don't treat it as an error if typecast_parse_time() returns 0 (as might happen if the remainder of the string is " BC"). * psycopg/typecast_datetime.c (typecast_PYDATE_cast): return NULL after setting DataError. (typecast_PYDATETIME_cast): same here. (typecast_PYTIME_cast): same here. * tests/test_dates.py (CommonDatetimeTestsMixin.test_parse_incomplete_date): test that parsing incomplete date values results in DataError. (CommonDatetimeTestsMixin.test_parse_incomplete_time): same for times. (CommonDatetimeTestsMixin.test_parse_incomplete_time): same for datetimes.
1 parent 1ea0cd4 commit cceaa73

File tree

5 files changed

+54
-237
lines changed

5 files changed

+54
-237
lines changed

ChangeLog

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
2008-03-17 James Henstridge <james@jamesh.id.au>
2+
3+
* psycopg/typecast.c (typecast_parse_time): give the correct
4+
return value for partially parsed time values.
5+
6+
* psycopg/typecast_mxdatetime.c (typecast_MXDATE_cast): return
7+
NULL after setting DataError. Also, don't treat it as an error if
8+
typecast_parse_time() returns 0 (as might happen if the remainder
9+
of the string is " BC").
10+
11+
* psycopg/typecast_datetime.c (typecast_PYDATE_cast): return NULL
12+
after setting DataError.
13+
(typecast_PYDATETIME_cast): same here.
14+
(typecast_PYTIME_cast): same here.
15+
16+
* tests/test_dates.py
17+
(CommonDatetimeTestsMixin.test_parse_incomplete_date): test that
18+
parsing incomplete date values results in DataError.
19+
(CommonDatetimeTestsMixin.test_parse_incomplete_time): same for
20+
times.
21+
(CommonDatetimeTestsMixin.test_parse_incomplete_time): same for
22+
datetimes.
23+
124
2008-03-07 Jason Erickson <jerickso@stickpeople.com>
225

326
* psycopg/pqpath.c (pq_raise): if PSYCOPG_EXTENSIONS is not

psycopg/typecast.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
146146
}
147147

148148
if (acc != -1) {
149-
if (cz == 2) { *ss = acc; cz += 1; }
149+
if (cz == 0) { *hh = acc; cz += 1; }
150+
else if (cz == 1) { *mm = acc; cz += 1; }
151+
else if (cz == 2) { *ss = acc; cz += 1; }
150152
else if (cz == 3) { *us = acc; cz += 1; }
151153
else if (cz == 4) { tzhh = acc; cz += 1; }
152154
else if (cz == 5) tzmm = acc;

psycopg/typecast_datetime.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ typecast_PYDATE_cast(const char *str, Py_ssize_t len, PyObject *curs)
5858
n, len, y, m, d);
5959
if (n != 3) {
6060
PyErr_SetString(DataError, "unable to parse date");
61+
return NULL;
6162
}
6263
else {
63-
if (y > 9999) y = 9999;
64+
if (y > 9999) y = 9999;
6465
obj = PyObject_CallFunction(pyDateTypeP, "iii", y, m, d);
6566
}
6667
}
@@ -98,6 +99,7 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
9899
tp, n, len, y, m, d);
99100
if (n != 3) {
100101
PyErr_SetString(DataError, "unable to parse date");
102+
return NULL;
101103
}
102104

103105
if (len > 0) {
@@ -108,6 +110,7 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
108110
n, len, hh, mm, ss, us, tz);
109111
if (n < 3 || n > 5) {
110112
PyErr_SetString(DataError, "unable to parse time");
113+
return NULL;
111114
}
112115
}
113116

@@ -116,7 +119,7 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
116119
ss -= 60;
117120
}
118121
if (y > 9999)
119-
y = 9999;
122+
y = 9999;
120123

121124
if (n == 5 && ((cursorObject*)curs)->tzinfo_factory != Py_None) {
122125
/* we have a time zone, calculate minutes and create
@@ -158,6 +161,7 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
158161

159162
if (n < 3 || n > 5) {
160163
PyErr_SetString(DataError, "unable to parse time");
164+
return NULL;
161165
}
162166
else {
163167
if (ss > 59) {

psycopg/typecast_mxdatetime.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ typecast_MXDATE_cast(const char *str, Py_ssize_t len, PyObject *curs)
5454
" y = %d, m = %d, d = %d", tp, n, len, y, m, d);
5555
if (n != 3) {
5656
PyErr_SetString(DataError, "unable to parse date");
57+
return NULL;
5758
}
5859

5960
if (len > 0) {
@@ -62,8 +63,9 @@ typecast_MXDATE_cast(const char *str, Py_ssize_t len, PyObject *curs)
6263
" len = " FORMAT_CODE_PY_SSIZE_T ","
6364
" hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
6465
n, len, hh, mm, ss, us, tz);
65-
if (n < 3 || n > 5) {
66+
if (n != 0 && (n < 3 || n > 5)) {
6667
PyErr_SetString(DataError, "unable to parse time");
68+
return NULL;
6769
}
6870
}
6971

tests/test_dates.py

Lines changed: 19 additions & 233 deletions
Original file line numberDiff line numberDiff line change
@@ -25,239 +25,9 @@ def test_parse_null_date(self):
2525
value = self.DATE(None, None)
2626
self.assertEqual(value, None)
2727

28-
def test_parse_time(self):
29-
value = self.TIME('13:30:29', None)
30-
self.assertNotEqual(value, None)
31-
self.assertEqual(value.hour, 13)
32-
self.assertEqual(value.minute, 30)
33-
self.assertEqual(value.second, 29)
34-
35-
def test_parse_null_time(self):
36-
value = self.TIME(None, None)
37-
self.assertEqual(value, None)
38-
39-
def test_parse_datetime(self):
40-
value = self.DATETIME('2007-01-01 13:30:29', None)
41-
self.assertNotEqual(value, None)
42-
self.assertEqual(value.year, 2007)
43-
self.assertEqual(value.month, 1)
44-
self.assertEqual(value.day, 1)
45-
self.assertEqual(value.hour, 13)
46-
self.assertEqual(value.minute, 30)
47-
self.assertEqual(value.second, 29)
48-
49-
def test_parse_null_datetime(self):
50-
value = self.DATETIME(None, None)
51-
self.assertEqual(value, None)
52-
53-
def test_parse_null_interval(self):
54-
value = self.INTERVAL(None, None)
55-
self.assertEqual(value, None)
56-
57-
58-
class DatetimeTests(unittest.TestCase, CommonDatetimeTestsMixin):
59-
"""Tests for the datetime based date handling in psycopg2."""
60-
61-
def setUp(self):
62-
self.DATE = psycopg2._psycopg.PYDATE
63-
self.TIME = psycopg2._psycopg.PYTIME
64-
self.DATETIME = psycopg2._psycopg.PYDATETIME
65-
self.INTERVAL = psycopg2._psycopg.PYINTERVAL
66-
67-
def test_parse_bc_date(self):
68-
# datetime does not support BC dates
69-
self.assertRaises(ValueError, self.DATE, '00042-01-01 BC', None)
70-
71-
def test_parse_bc_datetime(self):
72-
# datetime does not support BC dates
73-
self.assertRaises(ValueError, self.DATETIME,
74-
'00042-01-01 13:30:29 BC', None)
75-
76-
def test_parse_time_microseconds(self):
77-
value = self.TIME('13:30:29.123456', None)
78-
self.assertEqual(value.second, 29)
79-
self.assertEqual(value.microsecond, 123456)
80-
81-
def test_parse_datetime_microseconds(self):
82-
value = self.DATETIME('2007-01-01 13:30:29.123456', None)
83-
self.assertEqual(value.second, 29)
84-
self.assertEqual(value.microsecond, 123456)
85-
86-
def test_parse_interval(self):
87-
value = self.INTERVAL('42 days 12:34:56.123456', None)
88-
self.assertNotEqual(value, None)
89-
self.assertEqual(value.days, 42)
90-
self.assertEqual(value.seconds, 45296)
91-
self.assertEqual(value.microseconds, 123456)
92-
93-
def test_parse_negative_interval(self):
94-
value = self.INTERVAL('-42 days -12:34:56.123456', None)
95-
self.assertNotEqual(value, None)
96-
self.assertEqual(value.days, -43)
97-
self.assertEqual(value.seconds, 41103)
98-
self.assertEqual(value.microseconds, 876544)
99-
100-
def test_adapt_date(self):
101-
from datetime import date
102-
value = self.execute('select (%s)::date::text',
103-
[date(2007, 1, 1)])
104-
self.assertEqual(value, '2007-01-01')
105-
106-
def test_adapt_time(self):
107-
from datetime import time
108-
value = self.execute('select (%s)::time::text',
109-
[time(13, 30, 29)])
110-
self.assertEqual(value, '13:30:29')
111-
112-
def test_adapt_datetime(self):
113-
from datetime import datetime
114-
value = self.execute('select (%s)::timestamp::text',
115-
[datetime(2007, 1, 1, 13, 30, 29)])
116-
self.assertEqual(value, '2007-01-01 13:30:29')
117-
118-
def test_adapt_timedelta(self):
119-
from datetime import timedelta
120-
value = self.execute('select extract(epoch from (%s)::interval)',
121-
[timedelta(days=42, seconds=45296,
122-
microseconds=123456)])
123-
seconds = math.floor(value)
124-
self.assertEqual(seconds, 3674096)
125-
self.assertEqual(int(round((value - seconds) * 1000000)), 123456)
126-
127-
def test_adapt_megative_timedelta(self):
128-
from datetime import timedelta
129-
value = self.execute('select extract(epoch from (%s)::interval)',
130-
[timedelta(days=-42, seconds=45296,
131-
microseconds=123456)])
132-
seconds = math.floor(value)
133-
self.assertEqual(seconds, -3583504)
134-
self.assertEqual(int(round((value - seconds) * 1000000)), 123456)
135-
136-
137-
# Only run the datetime tests if psycopg was compiled with support.
138-
if not hasattr(psycopg2._psycopg, 'PYDATETIME'):
139-
del DatetimeTests
140-
141-
142-
class mxDateTimeTests(unittest.TestCase, CommonDatetimeTestsMixin):
143-
"""Tests for the mx.DateTime based date handling in psycopg2."""
144-
145-
def setUp(self):
146-
self.DATE = psycopg2._psycopg.MXDATE
147-
self.TIME = psycopg2._psycopg.MXTIME
148-
self.DATETIME = psycopg2._psycopg.MXDATETIME
149-
self.INTERVAL = psycopg2._psycopg.MXINTERVAL
150-
151-
def test_parse_bc_date(self):
152-
value = self.DATE('00042-01-01 BC', None)
153-
self.assertNotEqual(value, None)
154-
# mx.DateTime numbers BC dates from 0 rather than 1.
155-
self.assertEqual(value.year, -41)
156-
self.assertEqual(value.month, 1)
157-
self.assertEqual(value.day, 1)
158-
159-
def test_parse_bc_datetime(self):
160-
value = self.DATETIME('00042-01-01 13:30:29 BC', None)
161-
self.assertNotEqual(value, None)
162-
# mx.DateTime numbers BC dates from 0 rather than 1.
163-
self.assertEqual(value.year, -41)
164-
self.assertEqual(value.month, 1)
165-
self.assertEqual(value.day, 1)
166-
self.assertEqual(value.hour, 13)
167-
self.assertEqual(value.minute, 30)
168-
self.assertEqual(value.second, 29)
169-
170-
def test_parse_time_microseconds(self):
171-
value = self.TIME('13:30:29.123456', None)
172-
self.assertEqual(math.floor(value.second), 29)
173-
self.assertEqual(
174-
int((value.second - math.floor(value.second)) * 1000000), 123456)
175-
176-
def test_parse_datetime_microseconds(self):
177-
value = self.DATETIME('2007-01-01 13:30:29.123456', None)
178-
self.assertEqual(math.floor(value.second), 29)
179-
self.assertEqual(
180-
int((value.second - math.floor(value.second)) * 1000000), 123456)
181-
182-
def test_parse_interval(self):
183-
value = self.INTERVAL('42 days 05:50:05', None)
184-
self.assertNotEqual(value, None)
185-
self.assertEqual(value.day, 42)
186-
self.assertEqual(value.hour, 5)
187-
self.assertEqual(value.minute, 50)
188-
self.assertEqual(value.second, 5)
189-
190-
def test_adapt_time(self):
191-
from mx.DateTime import Time
192-
value = self.execute('select (%s)::time::text',
193-
[Time(13, 30, 29)])
194-
self.assertEqual(value, '13:30:29')
195-
196-
def test_adapt_datetime(self):
197-
from mx.DateTime import DateTime
198-
value = self.execute('select (%s)::timestamp::text',
199-
[DateTime(2007, 1, 1, 13, 30, 29.123456)])
200-
self.assertEqual(value, '2007-01-01 13:30:29.123456')
201-
202-
def test_adapt_bc_datetime(self):
203-
from mx.DateTime import DateTime
204-
value = self.execute('select (%s)::timestamp::text',
205-
[DateTime(-41, 1, 1, 13, 30, 29.123456)])
206-
self.assertEqual(value, '0042-01-01 13:30:29.123456 BC')
207-
208-
def test_adapt_timedelta(self):
209-
from mx.DateTime import DateTimeDeltaFrom
210-
value = self.execute('select extract(epoch from (%s)::interval)',
211-
[DateTimeDeltaFrom(days=42,
212-
seconds=45296.123456)])
213-
seconds = math.floor(value)
214-
self.assertEqual(seconds, 3674096)
215-
self.assertEqual(int(round((value - seconds) * 1000000)), 123456)
216-
217-
def test_adapt_megative_timedelta(self):
218-
from mx.DateTime import DateTimeDeltaFrom
219-
value = self.execute('select extract(epoch from (%s)::interval)',
220-
[DateTimeDeltaFrom(days=-42,
221-
seconds=45296.123456)])
222-
seconds = math.floor(value)
223-
self.assertEqual(seconds, -3583504)
224-
self.assertEqual(int(round((value - seconds) * 1000000)), 123456)
225-
226-
227-
# Only run the mx.DateTime tests if psycopg was compiled with support.
228-
if not hasattr(psycopg2._psycopg, 'MXDATETIME'):
229-
del mxDateTimeTests
230-
231-
232-
def test_suite():
233-
return unittest.TestLoader().loadTestsFromName(__name__)
234-
235-
#!/usr/bin/env python
236-
import math
237-
import unittest
238-
239-
import psycopg2
240-
import tests
241-
242-
243-
class CommonDatetimeTestsMixin:
244-
245-
def execute(self, *args):
246-
conn = psycopg2.connect("dbname=%s" % tests.dbname)
247-
curs = conn.cursor()
248-
curs.execute(*args)
249-
return curs.fetchone()[0]
250-
251-
def test_parse_date(self):
252-
value = self.DATE('2007-01-01', None)
253-
self.assertNotEqual(value, None)
254-
self.assertEqual(value.year, 2007)
255-
self.assertEqual(value.month, 1)
256-
self.assertEqual(value.day, 1)
257-
258-
def test_parse_null_date(self):
259-
value = self.DATE(None, None)
260-
self.assertEqual(value, None)
28+
def test_parse_incomplete_date(self):
29+
self.assertRaises(psycopg2.DataError, self.DATE, '2007', None)
30+
self.assertRaises(psycopg2.DataError, self.DATE, '2007-01', None)
26131

26232
def test_parse_time(self):
26333
value = self.TIME('13:30:29', None)
@@ -270,6 +40,10 @@ def test_parse_null_time(self):
27040
value = self.TIME(None, None)
27141
self.assertEqual(value, None)
27242

43+
def test_parse_incomplete_time(self):
44+
self.assertRaises(psycopg2.DataError, self.TIME, '13', None)
45+
self.assertRaises(psycopg2.DataError, self.TIME, '13:30', None)
46+
27347
def test_parse_datetime(self):
27448
value = self.DATETIME('2007-01-01 13:30:29', None)
27549
self.assertNotEqual(value, None)
@@ -284,6 +58,18 @@ def test_parse_null_datetime(self):
28458
value = self.DATETIME(None, None)
28559
self.assertEqual(value, None)
28660

61+
def test_parse_incomplete_time(self):
62+
self.assertRaises(psycopg2.DataError,
63+
self.DATETIME, '2007', None)
64+
self.assertRaises(psycopg2.DataError,
65+
self.DATETIME, '2007-01', None)
66+
self.assertRaises(psycopg2.DataError,
67+
self.DATETIME, '2007-01-01 13', None)
68+
self.assertRaises(psycopg2.DataError,
69+
self.DATETIME, '2007-01-01 13:30', None)
70+
self.assertRaises(psycopg2.DataError,
71+
self.DATETIME, '2007-01-01 13:30:29+00:10:50', None)
72+
28773
def test_parse_null_interval(self):
28874
value = self.INTERVAL(None, None)
28975
self.assertEqual(value, None)

0 commit comments

Comments
 (0)