mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-22 00:46:33 +03:00
Handle correctly timestamps with fractions of minute in the timezone offset
Close #1272.
This commit is contained in:
parent
5667026883
commit
476a969bd8
2
NEWS
2
NEWS
|
@ -6,6 +6,8 @@ What's new in psycopg 2.9
|
||||||
|
|
||||||
- ``with connection`` starts a transaction on autocommit transactions too
|
- ``with connection`` starts a transaction on autocommit transactions too
|
||||||
(:ticket:`#941`).
|
(:ticket:`#941`).
|
||||||
|
- Timezones with fractional minutes are supported on Python 3.7 and following
|
||||||
|
(:ticket:`#1272`).
|
||||||
- Escape table and column names in `~cursor.copy_from()` and
|
- Escape table and column names in `~cursor.copy_from()` and
|
||||||
`~cursor.copy_to()`.
|
`~cursor.copy_to()`.
|
||||||
- Connection exceptions with sqlstate ``08XXX`` reclassified as
|
- Connection exceptions with sqlstate ``08XXX`` reclassified as
|
||||||
|
|
|
@ -580,25 +580,33 @@ The PostgreSQL type :sql:`timestamp with time zone` (a.k.a.
|
||||||
a `~datetime.datetime.tzinfo` attribute set to a
|
a `~datetime.datetime.tzinfo` attribute set to a
|
||||||
`~psycopg2.tz.FixedOffsetTimezone` instance.
|
`~psycopg2.tz.FixedOffsetTimezone` instance.
|
||||||
|
|
||||||
>>> cur.execute("SET TIME ZONE 'Europe/Rome';") # UTC + 1 hour
|
>>> cur.execute("SET TIME ZONE 'Europe/Rome'") # UTC + 1 hour
|
||||||
>>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz;")
|
>>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz")
|
||||||
>>> cur.fetchone()[0].tzinfo
|
>>> cur.fetchone()[0].tzinfo
|
||||||
psycopg2.tz.FixedOffsetTimezone(offset=60, name=None)
|
psycopg2.tz.FixedOffsetTimezone(offset=60, name=None)
|
||||||
|
|
||||||
Note that only time zones with an integer number of minutes are supported:
|
.. note::
|
||||||
this is a limitation of the Python `datetime` module. A few historical time
|
|
||||||
zones had seconds in the UTC offset: these time zones will have the offset
|
|
||||||
rounded to the nearest minute, with an error of up to 30 seconds.
|
|
||||||
|
|
||||||
>>> cur.execute("SET TIME ZONE 'Asia/Calcutta';") # offset was +5:53:20
|
Before Python 3.7, the `datetime` module only supported timezones with an
|
||||||
>>> cur.execute("SELECT '1930-01-01 10:30:45'::timestamptz;")
|
integer number of minutes. A few historical time zones had seconds in the
|
||||||
>>> cur.fetchone()[0].tzinfo
|
UTC offset: these time zones will have the offset rounded to the nearest
|
||||||
psycopg2.tz.FixedOffsetTimezone(offset=353, name=None)
|
minute, with an error of up to 30 seconds, on Python versions before 3.7.
|
||||||
|
|
||||||
|
>>> cur.execute("SET TIME ZONE 'Asia/Calcutta'") # offset was +5:21:10
|
||||||
|
>>> cur.execute("SELECT '1900-01-01 10:30:45'::timestamptz")
|
||||||
|
>>> cur.fetchone()[0].tzinfo
|
||||||
|
# On Python 3.6: 5h, 21m
|
||||||
|
psycopg2.tz.FixedOffsetTimezone(offset=datetime.timedelta(0, 19260), name=None)
|
||||||
|
# On Python 3.7 and following: 5h, 21m, 10s
|
||||||
|
psycopg2.tz.FixedOffsetTimezone(offset=datetime.timedelta(seconds=19270), name=None)
|
||||||
|
|
||||||
.. versionchanged:: 2.2.2
|
.. versionchanged:: 2.2.2
|
||||||
timezones with seconds are supported (with rounding). Previously such
|
timezones with seconds are supported (with rounding). Previously such
|
||||||
timezones raised an error.
|
timezones raised an error.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
timezones with seconds are supported without rounding.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
double: Date objects; Infinite
|
double: Date objects; Infinite
|
||||||
|
|
45
lib/tz.py
45
lib/tz.py
|
@ -45,6 +45,11 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
offset and name that instance will be returned. This saves memory and
|
offset and name that instance will be returned. This saves memory and
|
||||||
improves comparability.
|
improves comparability.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
|
||||||
|
The constructor can take either a timedelta or a number of minutes of
|
||||||
|
offset. Previously only minutes were supported.
|
||||||
|
|
||||||
.. __: https://docs.python.org/library/datetime.html
|
.. __: https://docs.python.org/library/datetime.html
|
||||||
"""
|
"""
|
||||||
_name = None
|
_name = None
|
||||||
|
@ -54,7 +59,9 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
|
|
||||||
def __init__(self, offset=None, name=None):
|
def __init__(self, offset=None, name=None):
|
||||||
if offset is not None:
|
if offset is not None:
|
||||||
self._offset = datetime.timedelta(minutes=offset)
|
if not isinstance(offset, datetime.timedelta):
|
||||||
|
offset = datetime.timedelta(minutes=offset)
|
||||||
|
self._offset = offset
|
||||||
if name is not None:
|
if name is not None:
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
|
@ -70,13 +77,23 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
return tz
|
return tz
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60
|
|
||||||
return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \
|
return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \
|
||||||
% (offset_mins, self._name)
|
% (self._offset, self._name)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, FixedOffsetTimezone):
|
||||||
|
return self._offset == other._offset
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if isinstance(other, FixedOffsetTimezone):
|
||||||
|
return self._offset != other._offset
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
def __getinitargs__(self):
|
def __getinitargs__(self):
|
||||||
offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60
|
return self._offset, self._name
|
||||||
return offset_mins, self._name
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def utcoffset(self, dt):
|
||||||
return self._offset
|
return self._offset
|
||||||
|
@ -84,14 +101,16 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
def tzname(self, dt):
|
def tzname(self, dt):
|
||||||
if self._name is not None:
|
if self._name is not None:
|
||||||
return self._name
|
return self._name
|
||||||
else:
|
|
||||||
seconds = self._offset.seconds + self._offset.days * 86400
|
minutes, seconds = divmod(self._offset.total_seconds(), 60)
|
||||||
hours, seconds = divmod(seconds, 3600)
|
hours, minutes = divmod(minutes, 60)
|
||||||
minutes = seconds / 60
|
rv = "%+03d" % hours
|
||||||
if minutes:
|
if minutes or seconds:
|
||||||
return "%+03d:%d" % (hours, minutes)
|
rv += ":%02d" % minutes
|
||||||
else:
|
if seconds:
|
||||||
return "%+03d" % hours
|
rv += ":%02d" % seconds
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
def dst(self, dt):
|
def dst(self, dt):
|
||||||
return ZERO
|
return ZERO
|
||||||
|
|
|
@ -129,10 +129,11 @@ static PyObject *
|
||||||
_parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
_parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
{
|
{
|
||||||
PyObject* rv = NULL;
|
PyObject* rv = NULL;
|
||||||
|
PyObject *tzoff = NULL;
|
||||||
PyObject *tzinfo = NULL;
|
PyObject *tzinfo = NULL;
|
||||||
PyObject *tzinfo_factory;
|
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, tzsec=0;
|
||||||
const char *tp = NULL;
|
const char *tp = NULL;
|
||||||
|
|
||||||
Dprintf("typecast_PYDATETIMETZ_cast: s = %s", str);
|
Dprintf("typecast_PYDATETIMETZ_cast: s = %s", str);
|
||||||
|
@ -147,11 +148,11 @@ _parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tz);
|
n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tzsec);
|
||||||
Dprintf("typecast_PYDATETIMETZ_cast: n = %d,"
|
Dprintf("typecast_PYDATETIMETZ_cast: n = %d,"
|
||||||
" 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, tzsec = %d",
|
||||||
n, len, hh, mm, ss, us, tz);
|
n, len, hh, mm, ss, us, tzsec);
|
||||||
if (n < 3 || n > 6) {
|
if (n < 3 || n > 6) {
|
||||||
PyErr_SetString(DataError, "unable to parse time");
|
PyErr_SetString(DataError, "unable to parse time");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
@ -169,17 +170,20 @@ _parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
if (n >= 5 && tzinfo_factory != Py_None) {
|
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 */
|
||||||
Dprintf("typecast_PYDATETIMETZ_cast: UTC offset = %ds", tz);
|
Dprintf("typecast_PYDATETIMETZ_cast: UTC offset = %ds", tzsec);
|
||||||
|
|
||||||
/* The datetime module requires that time zone offsets be
|
#if PY_VERSION_HEX < 0x03070000
|
||||||
a whole number of minutes, so truncate the seconds to the
|
/* Before Python 3.7 the timezone offset had to be a whole number
|
||||||
closest minute. */
|
* of minutes, so round the seconds to the closest minute */
|
||||||
// printf("%d %d %d\n", tz, tzmin, round(tz / 60.0));
|
tzsec = 60 * (int)round(tzsec / 60.0);
|
||||||
if (!(tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
|
#endif
|
||||||
(int)round(tz / 60.0)))) {
|
if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; }
|
||||||
|
if (!(tzinfo = PyObject_CallFunctionObjArgs(
|
||||||
|
tzinfo_factory, tzoff, NULL))) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
tzinfo = Py_None;
|
tzinfo = Py_None;
|
||||||
}
|
}
|
||||||
|
@ -192,6 +196,7 @@ _parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
y, m, d, hh, mm, ss, us, tzinfo);
|
y, m, d, hh, mm, ss, us, tzinfo);
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
|
Py_XDECREF(tzoff);
|
||||||
Py_XDECREF(tzinfo);
|
Py_XDECREF(tzinfo);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -232,17 +237,18 @@ typecast_PYDATETIMETZ_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
static PyObject *
|
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* rv = NULL;
|
||||||
|
PyObject *tzoff = NULL;
|
||||||
PyObject *tzinfo = NULL;
|
PyObject *tzinfo = NULL;
|
||||||
PyObject *tzinfo_factory;
|
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, tzsec=0;
|
||||||
|
|
||||||
if (str == NULL) { Py_RETURN_NONE; }
|
if (str == NULL) { Py_RETURN_NONE; }
|
||||||
|
|
||||||
n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tz);
|
n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tzsec);
|
||||||
Dprintf("typecast_PYTIME_cast: n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", "
|
Dprintf("typecast_PYTIME_cast: n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", "
|
||||||
"hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
|
"hh = %d, mm = %d, ss = %d, us = %d, tzsec = %d",
|
||||||
n, len, hh, mm, ss, us, tz);
|
n, len, hh, mm, ss, us, tzsec);
|
||||||
|
|
||||||
if (n < 3 || n > 6) {
|
if (n < 3 || n > 6) {
|
||||||
PyErr_SetString(DataError, "unable to parse time");
|
PyErr_SetString(DataError, "unable to parse time");
|
||||||
|
@ -254,25 +260,32 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
}
|
}
|
||||||
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
|
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
|
||||||
if (n >= 5 && tzinfo_factory != Py_None) {
|
if (n >= 5 && tzinfo_factory != Py_None) {
|
||||||
/* we have a time zone, calculate minutes and create
|
/* we have a time zone, calculate seconds and create
|
||||||
appropriate tzinfo object calling the factory */
|
appropriate tzinfo object calling the factory */
|
||||||
Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tz);
|
Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tzsec);
|
||||||
|
|
||||||
/* The datetime module requires that time zone offsets be
|
#if PY_VERSION_HEX < 0x03070000
|
||||||
a whole number of minutes, so truncate the seconds to the
|
/* Before Python 3.7 the timezone offset had to be a whole number
|
||||||
closest minute. */
|
* of minutes, so round the seconds to the closest minute */
|
||||||
tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
|
tzsec = 60 * (int)round(tzsec / 60.0);
|
||||||
(int)round(tz / 60.0));
|
#endif
|
||||||
} else {
|
if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; }
|
||||||
|
if (!(tzinfo = PyObject_CallFunctionObjArgs(tzinfo_factory, tzoff, NULL))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
tzinfo = Py_None;
|
tzinfo = Py_None;
|
||||||
}
|
}
|
||||||
if (tzinfo != NULL) {
|
|
||||||
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO",
|
rv = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO",
|
||||||
hh, mm, ss, us, tzinfo);
|
hh, mm, ss, us, tzinfo);
|
||||||
Py_DECREF(tzinfo);
|
|
||||||
}
|
exit:
|
||||||
return obj;
|
Py_XDECREF(tzoff);
|
||||||
|
Py_XDECREF(tzinfo);
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||||
# License for more details.
|
# License for more details.
|
||||||
|
|
||||||
|
import sys
|
||||||
import math
|
import math
|
||||||
import pickle
|
import pickle
|
||||||
from datetime import date, datetime, time, timedelta
|
from datetime import date, datetime, time, timedelta
|
||||||
|
@ -157,17 +158,27 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
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)
|
||||||
self.check_time_tz("-01:15", -4500)
|
self.check_time_tz("-01:15", -4500)
|
||||||
# The Python datetime module does not support time zone
|
if sys.version_info < (3, 7):
|
||||||
# offsets that are not a whole number of minutes.
|
# The Python < 3.7 datetime module does not support time zone
|
||||||
# We round the offset to the nearest minute.
|
# offsets that are not a whole number of minutes.
|
||||||
self.check_time_tz("+01:15:00", 60 * (60 + 15))
|
# We round the offset to the nearest minute.
|
||||||
self.check_time_tz("+01:15:29", 60 * (60 + 15))
|
self.check_time_tz("+01:15:00", 60 * (60 + 15))
|
||||||
self.check_time_tz("+01:15:30", 60 * (60 + 16))
|
self.check_time_tz("+01:15:29", 60 * (60 + 15))
|
||||||
self.check_time_tz("+01:15:59", 60 * (60 + 16))
|
self.check_time_tz("+01:15:30", 60 * (60 + 16))
|
||||||
self.check_time_tz("-01:15:00", -60 * (60 + 15))
|
self.check_time_tz("+01:15:59", 60 * (60 + 16))
|
||||||
self.check_time_tz("-01:15:29", -60 * (60 + 15))
|
self.check_time_tz("-01:15:00", -60 * (60 + 15))
|
||||||
self.check_time_tz("-01:15:30", -60 * (60 + 16))
|
self.check_time_tz("-01:15:29", -60 * (60 + 15))
|
||||||
self.check_time_tz("-01:15:59", -60 * (60 + 16))
|
self.check_time_tz("-01:15:30", -60 * (60 + 16))
|
||||||
|
self.check_time_tz("-01:15:59", -60 * (60 + 16))
|
||||||
|
else:
|
||||||
|
self.check_time_tz("+01:15:00", 60 * (60 + 15))
|
||||||
|
self.check_time_tz("+01:15:29", 60 * (60 + 15) + 29)
|
||||||
|
self.check_time_tz("+01:15:30", 60 * (60 + 15) + 30)
|
||||||
|
self.check_time_tz("+01:15:59", 60 * (60 + 15) + 59)
|
||||||
|
self.check_time_tz("-01:15:00", -(60 * (60 + 15)))
|
||||||
|
self.check_time_tz("-01:15:29", -(60 * (60 + 15) + 29))
|
||||||
|
self.check_time_tz("-01:15:30", -(60 * (60 + 15) + 30))
|
||||||
|
self.check_time_tz("-01:15:59", -(60 * (60 + 15) + 59))
|
||||||
|
|
||||||
def check_datetime_tz(self, str_offset, offset):
|
def check_datetime_tz(self, str_offset, offset):
|
||||||
base = datetime(2007, 1, 1, 13, 30, 29)
|
base = datetime(2007, 1, 1, 13, 30, 29)
|
||||||
|
@ -192,17 +203,27 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
self.check_datetime_tz("-01", -3600)
|
self.check_datetime_tz("-01", -3600)
|
||||||
self.check_datetime_tz("+01:15", 4500)
|
self.check_datetime_tz("+01:15", 4500)
|
||||||
self.check_datetime_tz("-01:15", -4500)
|
self.check_datetime_tz("-01:15", -4500)
|
||||||
# The Python datetime module does not support time zone
|
if sys.version_info < (3, 7):
|
||||||
# offsets that are not a whole number of minutes.
|
# The Python < 3.7 datetime module does not support time zone
|
||||||
# We round the offset to the nearest minute.
|
# offsets that are not a whole number of minutes.
|
||||||
self.check_datetime_tz("+01:15:00", 60 * (60 + 15))
|
# We round the offset to the nearest minute.
|
||||||
self.check_datetime_tz("+01:15:29", 60 * (60 + 15))
|
self.check_datetime_tz("+01:15:00", 60 * (60 + 15))
|
||||||
self.check_datetime_tz("+01:15:30", 60 * (60 + 16))
|
self.check_datetime_tz("+01:15:29", 60 * (60 + 15))
|
||||||
self.check_datetime_tz("+01:15:59", 60 * (60 + 16))
|
self.check_datetime_tz("+01:15:30", 60 * (60 + 16))
|
||||||
self.check_datetime_tz("-01:15:00", -60 * (60 + 15))
|
self.check_datetime_tz("+01:15:59", 60 * (60 + 16))
|
||||||
self.check_datetime_tz("-01:15:29", -60 * (60 + 15))
|
self.check_datetime_tz("-01:15:00", -60 * (60 + 15))
|
||||||
self.check_datetime_tz("-01:15:30", -60 * (60 + 16))
|
self.check_datetime_tz("-01:15:29", -60 * (60 + 15))
|
||||||
self.check_datetime_tz("-01:15:59", -60 * (60 + 16))
|
self.check_datetime_tz("-01:15:30", -60 * (60 + 16))
|
||||||
|
self.check_datetime_tz("-01:15:59", -60 * (60 + 16))
|
||||||
|
else:
|
||||||
|
self.check_datetime_tz("+01:15:00", 60 * (60 + 15))
|
||||||
|
self.check_datetime_tz("+01:15:29", 60 * (60 + 15) + 29)
|
||||||
|
self.check_datetime_tz("+01:15:30", 60 * (60 + 15) + 30)
|
||||||
|
self.check_datetime_tz("+01:15:59", 60 * (60 + 15) + 59)
|
||||||
|
self.check_datetime_tz("-01:15:00", -(60 * (60 + 15)))
|
||||||
|
self.check_datetime_tz("-01:15:29", -(60 * (60 + 15) + 29))
|
||||||
|
self.check_datetime_tz("-01:15:30", -(60 * (60 + 15) + 30))
|
||||||
|
self.check_datetime_tz("-01:15:59", -(60 * (60 + 15) + 59))
|
||||||
|
|
||||||
def test_parse_time_no_timezone(self):
|
def test_parse_time_no_timezone(self):
|
||||||
self.assertEqual(self.TIME("13:30:29", self.curs).tzinfo, None)
|
self.assertEqual(self.TIME("13:30:29", self.curs).tzinfo, None)
|
||||||
|
@ -628,17 +649,27 @@ class FixedOffsetTimezoneTests(unittest.TestCase):
|
||||||
def test_repr_with_positive_offset(self):
|
def test_repr_with_positive_offset(self):
|
||||||
tzinfo = FixedOffsetTimezone(5 * 60)
|
tzinfo = FixedOffsetTimezone(5 * 60)
|
||||||
self.assertEqual(repr(tzinfo),
|
self.assertEqual(repr(tzinfo),
|
||||||
"psycopg2.tz.FixedOffsetTimezone(offset=300, name=None)")
|
"psycopg2.tz.FixedOffsetTimezone(offset=%r, name=None)"
|
||||||
|
% timedelta(minutes=5 * 60))
|
||||||
|
|
||||||
def test_repr_with_negative_offset(self):
|
def test_repr_with_negative_offset(self):
|
||||||
tzinfo = FixedOffsetTimezone(-5 * 60)
|
tzinfo = FixedOffsetTimezone(-5 * 60)
|
||||||
self.assertEqual(repr(tzinfo),
|
self.assertEqual(repr(tzinfo),
|
||||||
"psycopg2.tz.FixedOffsetTimezone(offset=-300, name=None)")
|
"psycopg2.tz.FixedOffsetTimezone(offset=%r, name=None)"
|
||||||
|
% timedelta(minutes=-5 * 60))
|
||||||
|
|
||||||
|
def test_init_with_timedelta(self):
|
||||||
|
td = timedelta(minutes=5 * 60)
|
||||||
|
tzinfo = FixedOffsetTimezone(td)
|
||||||
|
self.assertEqual(tzinfo, FixedOffsetTimezone(5 * 60))
|
||||||
|
self.assertEqual(repr(tzinfo),
|
||||||
|
"psycopg2.tz.FixedOffsetTimezone(offset=%r, name=None)" % td)
|
||||||
|
|
||||||
def test_repr_with_name(self):
|
def test_repr_with_name(self):
|
||||||
tzinfo = FixedOffsetTimezone(name="FOO")
|
tzinfo = FixedOffsetTimezone(name="FOO")
|
||||||
self.assertEqual(repr(tzinfo),
|
self.assertEqual(repr(tzinfo),
|
||||||
"psycopg2.tz.FixedOffsetTimezone(offset=0, name='FOO')")
|
"psycopg2.tz.FixedOffsetTimezone(offset=%r, name='FOO')"
|
||||||
|
% timedelta(0))
|
||||||
|
|
||||||
def test_instance_caching(self):
|
def test_instance_caching(self):
|
||||||
self.assert_(FixedOffsetTimezone(name="FOO")
|
self.assert_(FixedOffsetTimezone(name="FOO")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user