diff --git a/ChangeLog b/ChangeLog index d94fe6b9..60018ff1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +2008-03-17 James Henstridge + + * 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. + 2008-03-07 Jason Erickson * psycopg/pqpath.c (pq_raise): if PSYCOPG_EXTENSIONS is not diff --git a/psycopg/typecast.c b/psycopg/typecast.c index 6ed90a82..c7474e7d 100644 --- a/psycopg/typecast.c +++ b/psycopg/typecast.c @@ -146,7 +146,9 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len, } if (acc != -1) { - if (cz == 2) { *ss = acc; cz += 1; } + if (cz == 0) { *hh = acc; cz += 1; } + else if (cz == 1) { *mm = acc; cz += 1; } + else if (cz == 2) { *ss = acc; cz += 1; } else if (cz == 3) { *us = acc; cz += 1; } else if (cz == 4) { tzhh = acc; cz += 1; } else if (cz == 5) tzmm = acc; diff --git a/psycopg/typecast_datetime.c b/psycopg/typecast_datetime.c index 99f4b608..8144850b 100644 --- a/psycopg/typecast_datetime.c +++ b/psycopg/typecast_datetime.c @@ -58,9 +58,10 @@ typecast_PYDATE_cast(const char *str, Py_ssize_t len, PyObject *curs) n, len, y, m, d); if (n != 3) { PyErr_SetString(DataError, "unable to parse date"); + return NULL; } else { - if (y > 9999) y = 9999; + if (y > 9999) y = 9999; obj = PyObject_CallFunction(pyDateTypeP, "iii", y, m, d); } } @@ -98,6 +99,7 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs) tp, n, len, y, m, d); if (n != 3) { PyErr_SetString(DataError, "unable to parse date"); + return NULL; } if (len > 0) { @@ -108,6 +110,7 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs) n, len, hh, mm, ss, us, tz); if (n < 3 || n > 5) { PyErr_SetString(DataError, "unable to parse time"); + return NULL; } } @@ -116,7 +119,7 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs) ss -= 60; } if (y > 9999) - y = 9999; + y = 9999; if (n == 5 && ((cursorObject*)curs)->tzinfo_factory != Py_None) { /* 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) if (n < 3 || n > 5) { PyErr_SetString(DataError, "unable to parse time"); + return NULL; } else { if (ss > 59) { diff --git a/psycopg/typecast_mxdatetime.c b/psycopg/typecast_mxdatetime.c index a6e9509e..e030091d 100644 --- a/psycopg/typecast_mxdatetime.c +++ b/psycopg/typecast_mxdatetime.c @@ -54,6 +54,7 @@ typecast_MXDATE_cast(const char *str, Py_ssize_t len, PyObject *curs) " y = %d, m = %d, d = %d", tp, n, len, y, m, d); if (n != 3) { PyErr_SetString(DataError, "unable to parse date"); + return NULL; } if (len > 0) { @@ -62,8 +63,9 @@ typecast_MXDATE_cast(const char *str, Py_ssize_t len, PyObject *curs) " len = " FORMAT_CODE_PY_SSIZE_T "," " hh = %d, mm = %d, ss = %d, us = %d, tz = %d", n, len, hh, mm, ss, us, tz); - if (n < 3 || n > 5) { + if (n != 0 && (n < 3 || n > 5)) { PyErr_SetString(DataError, "unable to parse time"); + return NULL; } } diff --git a/tests/test_dates.py b/tests/test_dates.py index 376659f1..303391b2 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -25,6 +25,10 @@ class CommonDatetimeTestsMixin: value = self.DATE(None, None) self.assertEqual(value, None) + def test_parse_incomplete_date(self): + self.assertRaises(psycopg2.DataError, self.DATE, '2007', None) + self.assertRaises(psycopg2.DataError, self.DATE, '2007-01', None) + def test_parse_time(self): value = self.TIME('13:30:29', None) self.assertNotEqual(value, None) @@ -36,6 +40,10 @@ class CommonDatetimeTestsMixin: value = self.TIME(None, None) self.assertEqual(value, None) + def test_parse_incomplete_time(self): + self.assertRaises(psycopg2.DataError, self.TIME, '13', None) + self.assertRaises(psycopg2.DataError, self.TIME, '13:30', None) + def test_parse_datetime(self): value = self.DATETIME('2007-01-01 13:30:29', None) self.assertNotEqual(value, None) @@ -49,240 +57,18 @@ class CommonDatetimeTestsMixin: def test_parse_null_datetime(self): value = self.DATETIME(None, None) self.assertEqual(value, None) - - def test_parse_null_interval(self): - value = self.INTERVAL(None, None) - self.assertEqual(value, None) - - -class DatetimeTests(unittest.TestCase, CommonDatetimeTestsMixin): - """Tests for the datetime based date handling in psycopg2.""" - - def setUp(self): - self.DATE = psycopg2._psycopg.PYDATE - self.TIME = psycopg2._psycopg.PYTIME - self.DATETIME = psycopg2._psycopg.PYDATETIME - self.INTERVAL = psycopg2._psycopg.PYINTERVAL - - def test_parse_bc_date(self): - # datetime does not support BC dates - self.assertRaises(ValueError, self.DATE, '00042-01-01 BC', None) - - def test_parse_bc_datetime(self): - # datetime does not support BC dates - self.assertRaises(ValueError, self.DATETIME, - '00042-01-01 13:30:29 BC', None) - - def test_parse_time_microseconds(self): - value = self.TIME('13:30:29.123456', None) - self.assertEqual(value.second, 29) - self.assertEqual(value.microsecond, 123456) - - def test_parse_datetime_microseconds(self): - value = self.DATETIME('2007-01-01 13:30:29.123456', None) - self.assertEqual(value.second, 29) - self.assertEqual(value.microsecond, 123456) - - def test_parse_interval(self): - value = self.INTERVAL('42 days 12:34:56.123456', None) - self.assertNotEqual(value, None) - self.assertEqual(value.days, 42) - self.assertEqual(value.seconds, 45296) - self.assertEqual(value.microseconds, 123456) - - def test_parse_negative_interval(self): - value = self.INTERVAL('-42 days -12:34:56.123456', None) - self.assertNotEqual(value, None) - self.assertEqual(value.days, -43) - self.assertEqual(value.seconds, 41103) - self.assertEqual(value.microseconds, 876544) - - def test_adapt_date(self): - from datetime import date - value = self.execute('select (%s)::date::text', - [date(2007, 1, 1)]) - self.assertEqual(value, '2007-01-01') - - def test_adapt_time(self): - from datetime import time - value = self.execute('select (%s)::time::text', - [time(13, 30, 29)]) - self.assertEqual(value, '13:30:29') - - def test_adapt_datetime(self): - from datetime import datetime - value = self.execute('select (%s)::timestamp::text', - [datetime(2007, 1, 1, 13, 30, 29)]) - self.assertEqual(value, '2007-01-01 13:30:29') - - def test_adapt_timedelta(self): - from datetime import timedelta - value = self.execute('select extract(epoch from (%s)::interval)', - [timedelta(days=42, seconds=45296, - microseconds=123456)]) - seconds = math.floor(value) - self.assertEqual(seconds, 3674096) - self.assertEqual(int(round((value - seconds) * 1000000)), 123456) - - def test_adapt_megative_timedelta(self): - from datetime import timedelta - value = self.execute('select extract(epoch from (%s)::interval)', - [timedelta(days=-42, seconds=45296, - microseconds=123456)]) - seconds = math.floor(value) - self.assertEqual(seconds, -3583504) - self.assertEqual(int(round((value - seconds) * 1000000)), 123456) - - -# Only run the datetime tests if psycopg was compiled with support. -if not hasattr(psycopg2._psycopg, 'PYDATETIME'): - del DatetimeTests - - -class mxDateTimeTests(unittest.TestCase, CommonDatetimeTestsMixin): - """Tests for the mx.DateTime based date handling in psycopg2.""" - - def setUp(self): - self.DATE = psycopg2._psycopg.MXDATE - self.TIME = psycopg2._psycopg.MXTIME - self.DATETIME = psycopg2._psycopg.MXDATETIME - self.INTERVAL = psycopg2._psycopg.MXINTERVAL - - def test_parse_bc_date(self): - value = self.DATE('00042-01-01 BC', None) - self.assertNotEqual(value, None) - # mx.DateTime numbers BC dates from 0 rather than 1. - self.assertEqual(value.year, -41) - self.assertEqual(value.month, 1) - self.assertEqual(value.day, 1) - - def test_parse_bc_datetime(self): - value = self.DATETIME('00042-01-01 13:30:29 BC', None) - self.assertNotEqual(value, None) - # mx.DateTime numbers BC dates from 0 rather than 1. - self.assertEqual(value.year, -41) - self.assertEqual(value.month, 1) - self.assertEqual(value.day, 1) - self.assertEqual(value.hour, 13) - self.assertEqual(value.minute, 30) - self.assertEqual(value.second, 29) - - def test_parse_time_microseconds(self): - value = self.TIME('13:30:29.123456', None) - self.assertEqual(math.floor(value.second), 29) - self.assertEqual( - int((value.second - math.floor(value.second)) * 1000000), 123456) - - def test_parse_datetime_microseconds(self): - value = self.DATETIME('2007-01-01 13:30:29.123456', None) - self.assertEqual(math.floor(value.second), 29) - self.assertEqual( - int((value.second - math.floor(value.second)) * 1000000), 123456) - - def test_parse_interval(self): - value = self.INTERVAL('42 days 05:50:05', None) - self.assertNotEqual(value, None) - self.assertEqual(value.day, 42) - self.assertEqual(value.hour, 5) - self.assertEqual(value.minute, 50) - self.assertEqual(value.second, 5) - - def test_adapt_time(self): - from mx.DateTime import Time - value = self.execute('select (%s)::time::text', - [Time(13, 30, 29)]) - self.assertEqual(value, '13:30:29') - - def test_adapt_datetime(self): - from mx.DateTime import DateTime - value = self.execute('select (%s)::timestamp::text', - [DateTime(2007, 1, 1, 13, 30, 29.123456)]) - self.assertEqual(value, '2007-01-01 13:30:29.123456') - - def test_adapt_bc_datetime(self): - from mx.DateTime import DateTime - value = self.execute('select (%s)::timestamp::text', - [DateTime(-41, 1, 1, 13, 30, 29.123456)]) - self.assertEqual(value, '0042-01-01 13:30:29.123456 BC') - - def test_adapt_timedelta(self): - from mx.DateTime import DateTimeDeltaFrom - value = self.execute('select extract(epoch from (%s)::interval)', - [DateTimeDeltaFrom(days=42, - seconds=45296.123456)]) - seconds = math.floor(value) - self.assertEqual(seconds, 3674096) - self.assertEqual(int(round((value - seconds) * 1000000)), 123456) - - def test_adapt_megative_timedelta(self): - from mx.DateTime import DateTimeDeltaFrom - value = self.execute('select extract(epoch from (%s)::interval)', - [DateTimeDeltaFrom(days=-42, - seconds=45296.123456)]) - seconds = math.floor(value) - self.assertEqual(seconds, -3583504) - self.assertEqual(int(round((value - seconds) * 1000000)), 123456) - - -# Only run the mx.DateTime tests if psycopg was compiled with support. -if not hasattr(psycopg2._psycopg, 'MXDATETIME'): - del mxDateTimeTests - - -def test_suite(): - return unittest.TestLoader().loadTestsFromName(__name__) - -#!/usr/bin/env python -import math -import unittest - -import psycopg2 -import tests - - -class CommonDatetimeTestsMixin: - - def execute(self, *args): - conn = psycopg2.connect("dbname=%s" % tests.dbname) - curs = conn.cursor() - curs.execute(*args) - return curs.fetchone()[0] - - def test_parse_date(self): - value = self.DATE('2007-01-01', None) - self.assertNotEqual(value, None) - self.assertEqual(value.year, 2007) - self.assertEqual(value.month, 1) - self.assertEqual(value.day, 1) - - def test_parse_null_date(self): - value = self.DATE(None, None) - self.assertEqual(value, None) - - def test_parse_time(self): - value = self.TIME('13:30:29', None) - self.assertNotEqual(value, None) - self.assertEqual(value.hour, 13) - self.assertEqual(value.minute, 30) - self.assertEqual(value.second, 29) - - def test_parse_null_time(self): - value = self.TIME(None, None) - self.assertEqual(value, None) - - def test_parse_datetime(self): - value = self.DATETIME('2007-01-01 13:30:29', None) - self.assertNotEqual(value, None) - self.assertEqual(value.year, 2007) - self.assertEqual(value.month, 1) - self.assertEqual(value.day, 1) - self.assertEqual(value.hour, 13) - self.assertEqual(value.minute, 30) - self.assertEqual(value.second, 29) - - def test_parse_null_datetime(self): - value = self.DATETIME(None, None) - self.assertEqual(value, None) + + def test_parse_incomplete_time(self): + self.assertRaises(psycopg2.DataError, + self.DATETIME, '2007', None) + self.assertRaises(psycopg2.DataError, + self.DATETIME, '2007-01', None) + self.assertRaises(psycopg2.DataError, + self.DATETIME, '2007-01-01 13', None) + self.assertRaises(psycopg2.DataError, + self.DATETIME, '2007-01-01 13:30', None) + self.assertRaises(psycopg2.DataError, + self.DATETIME, '2007-01-01 13:30:29+00:10:50', None) def test_parse_null_interval(self): value = self.INTERVAL(None, None)