mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-26 19:03:43 +03:00
* 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:
parent
ba8be438bb
commit
2a94dfae47
17
ChangeLog
17
ChangeLog
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user