* tests/test_dates.py (DatetimeTests, mxDateTimeTests): full test

coverage for datetime and time strings with and without time zone
	information.

	* psycopg/typecast_datetime.c (typecast_PYDATETIME_cast): adjust
	to handle the changes in typecast_parse_time.
	(typecast_PYTIME_cast): add support for time zone aware time
	values.

	* psycopg/typecast_mxdatetime.c (typecast_MXDATE_cast): make sure
	that values with time zones are correctly processed (even though
	that means ignoring the time zone value).
	(typecast_MXTIME_cast): same here.

	* psycopg/typecast.c (typecast_parse_time): Update method to parse
	second resolution timezone offsets.
This commit is contained in:
James Henstridge 2009-02-17 15:03:33 +09:00
parent ba8be438bb
commit 2a94dfae47
5 changed files with 222 additions and 81 deletions

View File

@ -1,5 +1,22 @@
2009-02-17 James Henstridge <james@jamesh.id.au> 2009-02-17 James Henstridge <james@jamesh.id.au>
* tests/test_dates.py (DatetimeTests, mxDateTimeTests): full test
coverage for datetime and time strings with and without time zone
information.
* psycopg/typecast_datetime.c (typecast_PYDATETIME_cast): adjust
to handle the changes in typecast_parse_time.
(typecast_PYTIME_cast): add support for time zone aware time
values.
* psycopg/typecast_mxdatetime.c (typecast_MXDATE_cast): make sure
that values with time zones are correctly processed (even though
that means ignoring the time zone value).
(typecast_MXTIME_cast): same here.
* psycopg/typecast.c (typecast_parse_time): Update method to parse
second resolution timezone offsets.
* psycopg/typecast.c (typecast_parse_time): Fix up handling of * psycopg/typecast.c (typecast_parse_time): Fix up handling of
negative timezone offsets with a non-zero minutes field. negative timezone offsets with a non-zero minutes field.

View File

@ -96,7 +96,7 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
int* hh, int* mm, int* ss, int* us, int* tz) int* hh, int* mm, int* ss, int* us, int* tz)
{ {
int acc = -1, cz = 0; int acc = -1, cz = 0;
int tzs = 1, tzhh = 0, tzmm = 0; int tzsign = 1, tzhh = 0, tzmm = 0, tzss = 0;
int usd = 0; int usd = 0;
/* sets microseconds and timezone to 0 because they may be missing */ /* sets microseconds and timezone to 0 because they may be missing */
@ -105,7 +105,7 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
Dprintf("typecast_parse_time: len = " FORMAT_CODE_PY_SSIZE_T ", s = %s", Dprintf("typecast_parse_time: len = " FORMAT_CODE_PY_SSIZE_T ", s = %s",
*len, s); *len, s);
while (cz < 6 && *len > 0 && *s) { while (cz < 7 && *len > 0 && *s) {
switch (*s) { switch (*s) {
case ':': case ':':
if (cz == 0) *hh = acc; if (cz == 0) *hh = acc;
@ -113,6 +113,7 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
else if (cz == 2) *ss = acc; else if (cz == 2) *ss = acc;
else if (cz == 3) *us = acc; else if (cz == 3) *us = acc;
else if (cz == 4) tzhh = acc; else if (cz == 4) tzhh = acc;
else if (cz == 5) tzmm = acc;
acc = -1; cz++; acc = -1; cz++;
break; break;
case '.': case '.':
@ -125,7 +126,7 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
case '-': case '-':
/* seconds or microseconds here, anything else is an error */ /* seconds or microseconds here, anything else is an error */
if (cz < 2 || cz > 3) return -1; if (cz < 2 || cz > 3) return -1;
if (*s == '-') tzs = -1; if (*s == '-') tzsign = -1;
if (cz == 2) *ss = acc; if (cz == 2) *ss = acc;
else if (cz == 3) *us = acc; else if (cz == 3) *us = acc;
acc = -1; cz = 4; acc = -1; cz = 4;
@ -151,11 +152,12 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
else if (cz == 2) { *ss = acc; cz += 1; } else if (cz == 2) { *ss = acc; cz += 1; }
else if (cz == 3) { *us = acc; cz += 1; } else if (cz == 3) { *us = acc; cz += 1; }
else if (cz == 4) { tzhh = acc; cz += 1; } else if (cz == 4) { tzhh = acc; cz += 1; }
else if (cz == 5) tzmm = acc; else if (cz == 5) { tzmm = acc; cz += 1; }
else if (cz == 6) tzss = acc;
} }
if (t != NULL) *t = s; if (t != NULL) *t = s;
*tz = tzs * (tzhh * 60 + tzmm); *tz = tzsign * (3600 * tzhh + 60 * tzmm + tzss);
if (*us != 0) { if (*us != 0) {
while (usd++ < 6) *us *= 10; while (usd++ < 6) *us *= 10;

View File

@ -74,6 +74,8 @@ static PyObject *
typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs) typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
{ {
PyObject* obj = NULL; PyObject* obj = NULL;
PyObject *tzinfo = NULL;
PyObject *tzinfo_factory;
int n, y=0, m=0, d=0; int n, y=0, m=0, d=0;
int hh=0, mm=0, ss=0, us=0, tz=0; int hh=0, mm=0, ss=0, us=0, tz=0;
const char *tp = NULL; const char *tp = NULL;
@ -108,7 +110,7 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
" len = " FORMAT_CODE_PY_SSIZE_T "," " len = " FORMAT_CODE_PY_SSIZE_T ","
" hh = %d, mm = %d, ss = %d, us = %d, tz = %d", " hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
n, len, hh, mm, ss, us, tz); n, len, hh, mm, ss, us, tz);
if (n < 3 || n > 5) { if (n < 3 || n > 6) {
PyErr_SetString(DataError, "unable to parse time"); PyErr_SetString(DataError, "unable to parse time");
return NULL; return NULL;
} }
@ -121,24 +123,34 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
if (y > 9999) if (y > 9999)
y = 9999; y = 9999;
if (n == 5 && ((cursorObject*)curs)->tzinfo_factory != Py_None) { tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
if (n >= 5 && tzinfo_factory != Py_None) {
/* we have a time zone, calculate minutes and create /* we have a time zone, calculate minutes and create
appropriate tzinfo object calling the factory */ appropriate tzinfo object calling the factory */
PyObject *tzinfo; Dprintf("typecast_PYDATETIME_cast: UTC offset = %ds", tz);
Dprintf("typecast_PYDATETIME_cast: UTC offset = %dm", tz);
tzinfo = PyObject_CallFunction( /* The datetime module requires that time zone offsets be
((cursorObject*)curs)->tzinfo_factory, "i", tz); a whole number of minutes, so fail if we have a time
zone with a seconds offset.
*/
if (tz % 60 != 0) {
PyErr_Format(PyExc_ValueError, "time zone offset %d is not "
"a whole number of minutes", tz);
return NULL;
}
tzinfo = PyObject_CallFunction(tzinfo_factory, "i", tz / 60);
} else {
Py_INCREF(Py_None);
tzinfo = Py_None;
}
if (tzinfo != NULL) {
obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiiiO", obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiiiO",
y, m, d, hh, mm, ss, us, tzinfo); y, m, d, hh, mm, ss, us, tzinfo);
Dprintf("typecast_PYDATETIME_cast: tzinfo: %p, refcnt = " Dprintf("typecast_PYDATETIME_cast: tzinfo: %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T, FORMAT_CODE_PY_SSIZE_T,
tzinfo, tzinfo->ob_refcnt tzinfo, tzinfo->ob_refcnt
); );
Py_XDECREF(tzinfo); Py_DECREF(tzinfo);
}
else {
obj = PyObject_CallFunction(pyDateTimeTypeP, "iiiiiii",
y, m, d, hh, mm, ss, us);
} }
} }
return obj; return obj;
@ -150,6 +162,8 @@ static PyObject *
typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs) typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
{ {
PyObject* obj = NULL; PyObject* obj = NULL;
PyObject *tzinfo = NULL;
PyObject *tzinfo_factory;
int n, hh=0, mm=0, ss=0, us=0, tz=0; int n, hh=0, mm=0, ss=0, us=0, tz=0;
if (str == NULL) {Py_INCREF(Py_None); return Py_None;} if (str == NULL) {Py_INCREF(Py_None); return Py_None;}
@ -159,16 +173,38 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
"hh = %d, mm = %d, ss = %d, us = %d, tz = %d", "hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
n, len, hh, mm, ss, us, tz); n, len, hh, mm, ss, us, tz);
if (n < 3 || n > 5) { if (n < 3 || n > 6) {
PyErr_SetString(DataError, "unable to parse time"); PyErr_SetString(DataError, "unable to parse time");
return NULL; return NULL;
} }
else { if (ss > 59) {
if (ss > 59) { mm += 1;
mm += 1; ss -= 60;
ss -= 60; }
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
if (n >= 5 && tzinfo_factory != Py_None) {
/* we have a time zone, calculate minutes and create
appropriate tzinfo object calling the factory */
Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tz);
/* The datetime module requires that time zone offsets be
a whole number of minutes, so fail if we have a time
zone with a seconds offset.
*/
if (tz % 60 != 0) {
PyErr_Format(PyExc_ValueError, "time zone offset %d is not "
"a whole number of minutes", tz);
return NULL;
} }
obj = PyObject_CallFunction(pyTimeTypeP, "iiii", hh, mm, ss, us); tzinfo = PyObject_CallFunction(tzinfo_factory, "i", tz / 60);
} else {
Py_INCREF(Py_None);
tzinfo = Py_None;
}
if (tzinfo != NULL) {
obj = PyObject_CallFunction(pyTimeTypeP, "iiiiO",
hh, mm, ss, us, tzinfo);
Py_DECREF(tzinfo);
} }
return obj; return obj;
} }

View File

@ -63,7 +63,7 @@ typecast_MXDATE_cast(const char *str, Py_ssize_t len, PyObject *curs)
" len = " FORMAT_CODE_PY_SSIZE_T "," " len = " FORMAT_CODE_PY_SSIZE_T ","
" hh = %d, mm = %d, ss = %d, us = %d, tz = %d", " hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
n, len, hh, mm, ss, us, tz); n, len, hh, mm, ss, us, tz);
if (n != 0 && (n < 3 || n > 5)) { if (n != 0 && (n < 3 || n > 6)) {
PyErr_SetString(DataError, "unable to parse time"); PyErr_SetString(DataError, "unable to parse time");
return NULL; return NULL;
} }
@ -91,7 +91,7 @@ typecast_MXTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
Dprintf("typecast_MXTIME_cast: hh = %d, mm = %d, ss = %d, us = %d", Dprintf("typecast_MXTIME_cast: hh = %d, mm = %d, ss = %d, us = %d",
hh, mm, ss, us); hh, mm, ss, us);
if (n < 3 || n > 5) { if (n < 3 || n > 6) {
PyErr_SetString(DataError, "unable to parse time"); PyErr_SetString(DataError, "unable to parse time");
return NULL; return NULL;
} }

View File

@ -10,43 +10,41 @@ import tests
class CommonDatetimeTestsMixin: class CommonDatetimeTestsMixin:
def execute(self, *args): def execute(self, *args):
conn = psycopg2.connect(tests.dsn) self.curs.execute(*args)
curs = conn.cursor() return self.curs.fetchone()[0]
curs.execute(*args)
return curs.fetchone()[0]
def test_parse_date(self): def test_parse_date(self):
value = self.DATE('2007-01-01', None) value = self.DATE('2007-01-01', self.curs)
self.assertNotEqual(value, None) self.assertNotEqual(value, None)
self.assertEqual(value.year, 2007) self.assertEqual(value.year, 2007)
self.assertEqual(value.month, 1) self.assertEqual(value.month, 1)
self.assertEqual(value.day, 1) self.assertEqual(value.day, 1)
def test_parse_null_date(self): def test_parse_null_date(self):
value = self.DATE(None, None) value = self.DATE(None, self.curs)
self.assertEqual(value, None) self.assertEqual(value, None)
def test_parse_incomplete_date(self): def test_parse_incomplete_date(self):
self.assertRaises(psycopg2.DataError, self.DATE, '2007', None) self.assertRaises(psycopg2.DataError, self.DATE, '2007', self.curs)
self.assertRaises(psycopg2.DataError, self.DATE, '2007-01', None) self.assertRaises(psycopg2.DataError, self.DATE, '2007-01', self.curs)
def test_parse_time(self): def test_parse_time(self):
value = self.TIME('13:30:29', None) value = self.TIME('13:30:29', self.curs)
self.assertNotEqual(value, None) self.assertNotEqual(value, None)
self.assertEqual(value.hour, 13) self.assertEqual(value.hour, 13)
self.assertEqual(value.minute, 30) self.assertEqual(value.minute, 30)
self.assertEqual(value.second, 29) self.assertEqual(value.second, 29)
def test_parse_null_time(self): def test_parse_null_time(self):
value = self.TIME(None, None) value = self.TIME(None, self.curs)
self.assertEqual(value, None) self.assertEqual(value, None)
def test_parse_incomplete_time(self): def test_parse_incomplete_time(self):
self.assertRaises(psycopg2.DataError, self.TIME, '13', None) self.assertRaises(psycopg2.DataError, self.TIME, '13', self.curs)
self.assertRaises(psycopg2.DataError, self.TIME, '13:30', None) self.assertRaises(psycopg2.DataError, self.TIME, '13:30', self.curs)
def test_parse_datetime(self): def test_parse_datetime(self):
value = self.DATETIME('2007-01-01 13:30:29', None) value = self.DATETIME('2007-01-01 13:30:29', self.curs)
self.assertNotEqual(value, None) self.assertNotEqual(value, None)
self.assertEqual(value.year, 2007) self.assertEqual(value.year, 2007)
self.assertEqual(value.month, 1) self.assertEqual(value.month, 1)
@ -56,23 +54,21 @@ class CommonDatetimeTestsMixin:
self.assertEqual(value.second, 29) self.assertEqual(value.second, 29)
def test_parse_null_datetime(self): def test_parse_null_datetime(self):
value = self.DATETIME(None, None) value = self.DATETIME(None, self.curs)
self.assertEqual(value, None) self.assertEqual(value, None)
def test_parse_incomplete_time(self): def test_parse_incomplete_time(self):
self.assertRaises(psycopg2.DataError, self.assertRaises(psycopg2.DataError,
self.DATETIME, '2007', None) self.DATETIME, '2007', self.curs)
self.assertRaises(psycopg2.DataError, self.assertRaises(psycopg2.DataError,
self.DATETIME, '2007-01', None) self.DATETIME, '2007-01', self.curs)
self.assertRaises(psycopg2.DataError, self.assertRaises(psycopg2.DataError,
self.DATETIME, '2007-01-01 13', None) self.DATETIME, '2007-01-01 13', self.curs)
self.assertRaises(psycopg2.DataError, self.assertRaises(psycopg2.DataError,
self.DATETIME, '2007-01-01 13:30', None) self.DATETIME, '2007-01-01 13:30', self.curs)
self.assertRaises(psycopg2.DataError,
self.DATETIME, '2007-01-01 13:30:29+00:10:50', None)
def test_parse_null_interval(self): def test_parse_null_interval(self):
value = self.INTERVAL(None, None) value = self.INTERVAL(None, self.curs)
self.assertEqual(value, None) self.assertEqual(value, None)
@ -80,80 +76,137 @@ class DatetimeTests(unittest.TestCase, CommonDatetimeTestsMixin):
"""Tests for the datetime based date handling in psycopg2.""" """Tests for the datetime based date handling in psycopg2."""
def setUp(self): def setUp(self):
self.conn = psycopg2.connect(tests.dsn)
self.curs = self.conn.cursor()
self.DATE = psycopg2._psycopg.PYDATE self.DATE = psycopg2._psycopg.PYDATE
self.TIME = psycopg2._psycopg.PYTIME self.TIME = psycopg2._psycopg.PYTIME
self.DATETIME = psycopg2._psycopg.PYDATETIME self.DATETIME = psycopg2._psycopg.PYDATETIME
self.INTERVAL = psycopg2._psycopg.PYINTERVAL self.INTERVAL = psycopg2._psycopg.PYINTERVAL
def tearDown(self):
self.conn.close()
def test_parse_bc_date(self): def test_parse_bc_date(self):
# datetime does not support BC dates # datetime does not support BC dates
self.assertRaises(ValueError, self.DATE, '00042-01-01 BC', None) self.assertRaises(ValueError, self.DATE, '00042-01-01 BC', self.curs)
def test_parse_bc_datetime(self): def test_parse_bc_datetime(self):
# datetime does not support BC dates # datetime does not support BC dates
self.assertRaises(ValueError, self.DATETIME, self.assertRaises(ValueError, self.DATETIME,
'00042-01-01 13:30:29 BC', None) '00042-01-01 13:30:29 BC', self.curs)
def test_parse_time_microseconds(self): def test_parse_time_microseconds(self):
value = self.TIME('13:30:29.123456', None) value = self.TIME('13:30:29.123456', self.curs)
self.assertEqual(value.second, 29) self.assertEqual(value.second, 29)
self.assertEqual(value.microsecond, 123456) self.assertEqual(value.microsecond, 123456)
def test_parse_datetime_microseconds(self): def test_parse_datetime_microseconds(self):
value = self.DATETIME('2007-01-01 13:30:29.123456', None) value = self.DATETIME('2007-01-01 13:30:29.123456', self.curs)
self.assertEqual(value.second, 29) self.assertEqual(value.second, 29)
self.assertEqual(value.microsecond, 123456) self.assertEqual(value.microsecond, 123456)
def check_timezone(self, curs, str_offset, offset): def check_time_tz(self, str_offset, offset):
from datetime import time, timedelta
base = time(13, 30, 29)
base_str = '13:30:29'
value = self.TIME(base_str + str_offset, self.curs)
# Value has time zone info and correct UTC offset.
self.assertNotEqual(value.tzinfo, None),
self.assertEqual(value.utcoffset(), timedelta(seconds=offset))
# Time portion is correct.
self.assertEqual(value.replace(tzinfo=None), base)
def test_parse_time_timezone(self):
self.check_time_tz("+01", 3600)
self.check_time_tz("-01", -3600)
self.check_time_tz("+01:15", 4500)
self.check_time_tz("-01:15", -4500)
# The Python datetime module does not support time zone
# offsets that are not a whole number of minutes, so we get an
# error here. Check that we are generating an understandable
# error message.
try:
self.check_time_tz("+01:15:42", 4542)
except ValueError, exc:
self.assertEqual(exc.message, "time zone offset 4542 is not a "
"whole number of minutes")
else:
self.fail("Expected ValueError")
try:
self.check_time_tz("-01:15:42", -4542)
except ValueError, exc:
self.assertEqual(exc.message, "time zone offset -4542 is not a "
"whole number of minutes")
else:
self.fail("Expected ValueError")
def check_datetime_tz(self, str_offset, offset):
from datetime import datetime, timedelta from datetime import datetime, timedelta
base = datetime(2007, 1, 1, 13, 30, 29) base = datetime(2007, 1, 1, 13, 30, 29)
base_str = '2007-01-01 13:30:29' base_str = '2007-01-01 13:30:29'
value = self.DATETIME(base_str + str_offset, curs) value = self.DATETIME(base_str + str_offset, self.curs)
# tzinfo instance is correct. # Value has time zone info and correct UTC offset.
self.assertTrue(isinstance(value.tzinfo, FixedOffsetTimezone), self.assertNotEqual(value.tzinfo, None),
"value's timezone is not a FixedOffsetTimezone") self.assertEqual(value.utcoffset(), timedelta(seconds=offset))
self.assertEqual(value.tzinfo._offset, timedelta(seconds=offset))
# Datetime is correct. # Datetime is correct.
self.assertEqual(value.replace(tzinfo=None), base) self.assertEqual(value.replace(tzinfo=None), base)
# Offset from UTC is correct. # Conversion to UTC produces the expected offset.
UTC = FixedOffsetTimezone(0, "UTC") UTC = FixedOffsetTimezone(0, "UTC")
value_utc = value.astimezone(UTC).replace(tzinfo=None) value_utc = value.astimezone(UTC).replace(tzinfo=None)
self.assertEqual(base - value_utc, timedelta(seconds=offset)) self.assertEqual(base - value_utc, timedelta(seconds=offset))
def test_parse_datetime_timezone_hours(self): def test_parse_datetime_timezone(self):
conn = psycopg2.connect(tests.dsn) self.check_datetime_tz("+01", 3600)
curs = conn.cursor() self.check_datetime_tz("-01", -3600)
self.check_timezone(curs, "+01", 3600) self.check_datetime_tz("+01:15", 4500)
self.check_timezone(curs, "-01", -3600) self.check_datetime_tz("-01:15", -4500)
# The Python datetime module does not support time zone
# offsets that are not a whole number of minutes, so we get an
# error here. Check that we are generating an understandable
# error message.
try:
self.check_datetime_tz("+01:15:42", 4542)
except ValueError, exc:
self.assertEqual(exc.message, "time zone offset 4542 is not a "
"whole number of minutes")
else:
self.fail("Expected ValueError")
def test_parse_datetime_timezone_hours_minutes(self): try:
conn = psycopg2.connect(tests.dsn) self.check_datetime_tz("-01:15:42", -4542)
curs = conn.cursor() except ValueError, exc:
self.check_timezone(curs, "+01:15", 4500) self.assertEqual(exc.message, "time zone offset -4542 is not a "
self.check_timezone(curs, "-01:15", -4500) "whole number of minutes")
else:
self.fail("Expected ValueError")
# This test is disabled because we don't support parsing second def test_parse_time_no_timezone(self):
# resolution timezone offsets and Python wouldn't handle them even self.assertEqual(self.TIME("13:30:29", self.curs).tzinfo, None)
# if we did. self.assertEqual(self.TIME("13:30:29.123456", self.curs).tzinfo, None)
def disabled_test_parse_datetime_timezone_hours_minutes_seconds(self):
conn = psycopg2.connect(tests.dsn) def test_parse_datetime_no_timezone(self):
curs = conn.cursor() self.assertEqual(
self.check_timezone(curs, "+01:15:42", 4542) self.DATETIME("2007-01-01 13:30:29", self.curs).tzinfo, None)
self.check_timezone(curs, "-01:15:42", -4542) self.assertEqual(
self.DATETIME("2007-01-01 13:30:29.123456", self.curs).tzinfo, None)
def test_parse_interval(self): def test_parse_interval(self):
value = self.INTERVAL('42 days 12:34:56.123456', None) value = self.INTERVAL('42 days 12:34:56.123456', self.curs)
self.assertNotEqual(value, None) self.assertNotEqual(value, None)
self.assertEqual(value.days, 42) self.assertEqual(value.days, 42)
self.assertEqual(value.seconds, 45296) self.assertEqual(value.seconds, 45296)
self.assertEqual(value.microseconds, 123456) self.assertEqual(value.microseconds, 123456)
def test_parse_negative_interval(self): def test_parse_negative_interval(self):
value = self.INTERVAL('-42 days -12:34:56.123456', None) value = self.INTERVAL('-42 days -12:34:56.123456', self.curs)
self.assertNotEqual(value, None) self.assertNotEqual(value, None)
self.assertEqual(value.days, -43) self.assertEqual(value.days, -43)
self.assertEqual(value.seconds, 41103) self.assertEqual(value.seconds, 41103)
@ -205,13 +258,18 @@ class mxDateTimeTests(unittest.TestCase, CommonDatetimeTestsMixin):
"""Tests for the mx.DateTime based date handling in psycopg2.""" """Tests for the mx.DateTime based date handling in psycopg2."""
def setUp(self): def setUp(self):
self.conn = psycopg2.connect(tests.dsn)
self.curs = self.conn.cursor()
self.DATE = psycopg2._psycopg.MXDATE self.DATE = psycopg2._psycopg.MXDATE
self.TIME = psycopg2._psycopg.MXTIME self.TIME = psycopg2._psycopg.MXTIME
self.DATETIME = psycopg2._psycopg.MXDATETIME self.DATETIME = psycopg2._psycopg.MXDATETIME
self.INTERVAL = psycopg2._psycopg.MXINTERVAL self.INTERVAL = psycopg2._psycopg.MXINTERVAL
def tearDown(self):
self.conn.close()
def test_parse_bc_date(self): def test_parse_bc_date(self):
value = self.DATE('00042-01-01 BC', None) value = self.DATE('00042-01-01 BC', self.curs)
self.assertNotEqual(value, None) self.assertNotEqual(value, None)
# mx.DateTime numbers BC dates from 0 rather than 1. # mx.DateTime numbers BC dates from 0 rather than 1.
self.assertEqual(value.year, -41) self.assertEqual(value.year, -41)
@ -219,7 +277,7 @@ class mxDateTimeTests(unittest.TestCase, CommonDatetimeTestsMixin):
self.assertEqual(value.day, 1) self.assertEqual(value.day, 1)
def test_parse_bc_datetime(self): def test_parse_bc_datetime(self):
value = self.DATETIME('00042-01-01 13:30:29 BC', None) value = self.DATETIME('00042-01-01 13:30:29 BC', self.curs)
self.assertNotEqual(value, None) self.assertNotEqual(value, None)
# mx.DateTime numbers BC dates from 0 rather than 1. # mx.DateTime numbers BC dates from 0 rather than 1.
self.assertEqual(value.year, -41) self.assertEqual(value.year, -41)
@ -230,19 +288,47 @@ class mxDateTimeTests(unittest.TestCase, CommonDatetimeTestsMixin):
self.assertEqual(value.second, 29) self.assertEqual(value.second, 29)
def test_parse_time_microseconds(self): def test_parse_time_microseconds(self):
value = self.TIME('13:30:29.123456', None) value = self.TIME('13:30:29.123456', self.curs)
self.assertEqual(math.floor(value.second), 29) self.assertEqual(math.floor(value.second), 29)
self.assertEqual( self.assertEqual(
int((value.second - math.floor(value.second)) * 1000000), 123456) int((value.second - math.floor(value.second)) * 1000000), 123456)
def test_parse_datetime_microseconds(self): def test_parse_datetime_microseconds(self):
value = self.DATETIME('2007-01-01 13:30:29.123456', None) value = self.DATETIME('2007-01-01 13:30:29.123456', self.curs)
self.assertEqual(math.floor(value.second), 29) self.assertEqual(math.floor(value.second), 29)
self.assertEqual( self.assertEqual(
int((value.second - math.floor(value.second)) * 1000000), 123456) int((value.second - math.floor(value.second)) * 1000000), 123456)
def test_parse_time_timezone(self):
# Time zone information is ignored.
from mx.DateTime import Time
expected = Time(13, 30, 29)
self.assertEqual(expected, self.TIME("13:30:29+01", self.curs))
self.assertEqual(expected, self.TIME("13:30:29-01", self.curs))
self.assertEqual(expected, self.TIME("13:30:29+01:15", self.curs))
self.assertEqual(expected, self.TIME("13:30:29-01:15", self.curs))
self.assertEqual(expected, self.TIME("13:30:29+01:15:42", self.curs))
self.assertEqual(expected, self.TIME("13:30:29-01:15:42", self.curs))
def test_parse_datetime_timezone(self):
# Time zone information is ignored.
from mx.DateTime import DateTime
expected = DateTime(2007, 1, 1, 13, 30, 29)
self.assertEqual(
expected, self.DATETIME("2007-01-01 13:30:29+01", self.curs))
self.assertEqual(
expected, self.DATETIME("2007-01-01 13:30:29-01", self.curs))
self.assertEqual(
expected, self.DATETIME("2007-01-01 13:30:29+01:15", self.curs))
self.assertEqual(
expected, self.DATETIME("2007-01-01 13:30:29-01:15", self.curs))
self.assertEqual(
expected, self.DATETIME("2007-01-01 13:30:29+01:15:42", self.curs))
self.assertEqual(
expected, self.DATETIME("2007-01-01 13:30:29-01:15:42", self.curs))
def test_parse_interval(self): def test_parse_interval(self):
value = self.INTERVAL('42 days 05:50:05', None) value = self.INTERVAL('42 days 05:50:05', self.curs)
self.assertNotEqual(value, None) self.assertNotEqual(value, None)
self.assertEqual(value.day, 42) self.assertEqual(value.day, 42)
self.assertEqual(value.hour, 5) self.assertEqual(value.hour, 5)