mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-04-28 12:13:42 +03:00
Handle time zones with seconds in the UTC offset.
The offset is rounded to the nearest minute.
This commit is contained in:
parent
e3d489f701
commit
305d86f38a
|
@ -1,3 +1,8 @@
|
||||||
|
2010-05-20 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||||
|
|
||||||
|
* psycopg/typecast_datetime.c: Round seconds in historical timezones to
|
||||||
|
the nearest minute.
|
||||||
|
|
||||||
2010-05-17 Federico Di Gregorio <fog@initd.org>
|
2010-05-17 Federico Di Gregorio <fog@initd.org>
|
||||||
|
|
||||||
* Release 2.2.1.
|
* Release 2.2.1.
|
||||||
|
|
|
@ -365,6 +365,38 @@ the connection or globally: see the function
|
||||||
and then forget about this story.
|
and then forget about this story.
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: Time Zones
|
||||||
|
|
||||||
|
.. _tz-handling:
|
||||||
|
|
||||||
|
Time zones handling
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The PostgreSQL type :sql:`timestamp with time zone` is converted into Python
|
||||||
|
`!datetime` objects with a `!tzinfo` attribute set to a
|
||||||
|
`~psycopg2.tz.FixedOffsetTimezone` instance.
|
||||||
|
|
||||||
|
>>> cur.execute("SET TIME ZONE 'Europe/Rome';") # UTC + 1 hour
|
||||||
|
>>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz;")
|
||||||
|
>>> cur.fetchone()[0].tzinfo
|
||||||
|
psycopg2.tz.FixedOffsetTimezone(offset=60, name=None)
|
||||||
|
|
||||||
|
Notice that only time zones with an integer number of minutes are supported:
|
||||||
|
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
|
||||||
|
>>> cur.execute("SELECT '1930-01-01 10:30:45'::timestamptz;")
|
||||||
|
>>> cur.fetchone()[0].tzinfo
|
||||||
|
psycopg2.tz.FixedOffsetTimezone(offset=353, name=None)
|
||||||
|
|
||||||
|
.. versionchanged:: 2.2.2
|
||||||
|
timezones with seconds are supported (with rounding). Previously such
|
||||||
|
timezones raised an error. In order to deal with them in previous
|
||||||
|
versions use `psycopg2.extras.register_tstz_w_secs`.
|
||||||
|
|
||||||
|
|
||||||
.. index:: Transaction, Begin, Commit, Rollback, Autocommit
|
.. index:: Transaction, Begin, Commit, Rollback, Autocommit
|
||||||
|
|
||||||
|
|
|
@ -134,15 +134,11 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
Dprintf("typecast_PYDATETIME_cast: UTC offset = %ds", tz);
|
Dprintf("typecast_PYDATETIME_cast: UTC offset = %ds", tz);
|
||||||
|
|
||||||
/* The datetime module requires that time zone offsets be
|
/* The datetime module requires that time zone offsets be
|
||||||
a whole number of minutes, so fail if we have a time
|
a whole number of minutes, so truncate the seconds to the
|
||||||
zone with a seconds offset.
|
closest minute. */
|
||||||
*/
|
// printf("%d %d %d\n", tz, tzmin, round(tz / 60.0));
|
||||||
if (tz % 60 != 0) {
|
tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
|
||||||
PyErr_Format(PyExc_ValueError, "time zone offset %d is not "
|
(int)round(tz / 60.0));
|
||||||
"a whole number of minutes", tz);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
tzinfo = PyObject_CallFunction(tzinfo_factory, "i", tz / 60);
|
|
||||||
} else {
|
} else {
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
tzinfo = Py_None;
|
tzinfo = Py_None;
|
||||||
|
@ -192,15 +188,10 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tz);
|
Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tz);
|
||||||
|
|
||||||
/* The datetime module requires that time zone offsets be
|
/* The datetime module requires that time zone offsets be
|
||||||
a whole number of minutes, so fail if we have a time
|
a whole number of minutes, so truncate the seconds to the
|
||||||
zone with a seconds offset.
|
closest minute. */
|
||||||
*/
|
tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
|
||||||
if (tz % 60 != 0) {
|
(int)round(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 {
|
} else {
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
tzinfo = Py_None;
|
tzinfo = Py_None;
|
||||||
|
|
|
@ -124,24 +124,16 @@ class DatetimeTests(unittest.TestCase, CommonDatetimeTestsMixin):
|
||||||
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
|
# The Python datetime module does not support time zone
|
||||||
# offsets that are not a whole number of minutes, so we get an
|
# offsets that are not a whole number of minutes.
|
||||||
# error here. Check that we are generating an understandable
|
# We round the offset to the nearest minute.
|
||||||
# error message.
|
self.check_time_tz("+01:15:00", 60 * (60 + 15))
|
||||||
try:
|
self.check_time_tz("+01:15:29", 60 * (60 + 15))
|
||||||
self.check_time_tz("+01:15:42", 4542)
|
self.check_time_tz("+01:15:30", 60 * (60 + 16))
|
||||||
except ValueError, exc:
|
self.check_time_tz("+01:15:59", 60 * (60 + 16))
|
||||||
self.assertEqual(str(exc), "time zone offset 4542 is not a "
|
self.check_time_tz("-01:15:00", -60 * (60 + 15))
|
||||||
"whole number of minutes")
|
self.check_time_tz("-01:15:29", -60 * (60 + 15))
|
||||||
else:
|
self.check_time_tz("-01:15:30", -60 * (60 + 16))
|
||||||
self.fail("Expected ValueError")
|
self.check_time_tz("-01:15:59", -60 * (60 + 16))
|
||||||
|
|
||||||
try:
|
|
||||||
self.check_time_tz("-01:15:42", -4542)
|
|
||||||
except ValueError, exc:
|
|
||||||
self.assertEqual(str(exc), "time zone offset -4542 is not a "
|
|
||||||
"whole number of minutes")
|
|
||||||
else:
|
|
||||||
self.fail("Expected ValueError")
|
|
||||||
|
|
||||||
def check_datetime_tz(self, str_offset, offset):
|
def check_datetime_tz(self, str_offset, offset):
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
@ -168,24 +160,16 @@ class DatetimeTests(unittest.TestCase, CommonDatetimeTestsMixin):
|
||||||
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
|
# The Python datetime module does not support time zone
|
||||||
# offsets that are not a whole number of minutes, so we get an
|
# offsets that are not a whole number of minutes.
|
||||||
# error here. Check that we are generating an understandable
|
# We round the offset to the nearest minute.
|
||||||
# error message.
|
self.check_datetime_tz("+01:15:00", 60 * (60 + 15))
|
||||||
try:
|
self.check_datetime_tz("+01:15:29", 60 * (60 + 15))
|
||||||
self.check_datetime_tz("+01:15:42", 4542)
|
self.check_datetime_tz("+01:15:30", 60 * (60 + 16))
|
||||||
except ValueError, exc:
|
self.check_datetime_tz("+01:15:59", 60 * (60 + 16))
|
||||||
self.assertEqual(str(exc), "time zone offset 4542 is not a "
|
self.check_datetime_tz("-01:15:00", -60 * (60 + 15))
|
||||||
"whole number of minutes")
|
self.check_datetime_tz("-01:15:29", -60 * (60 + 15))
|
||||||
else:
|
self.check_datetime_tz("-01:15:30", -60 * (60 + 16))
|
||||||
self.fail("Expected ValueError")
|
self.check_datetime_tz("-01:15:59", -60 * (60 + 16))
|
||||||
|
|
||||||
try:
|
|
||||||
self.check_datetime_tz("-01:15:42", -4542)
|
|
||||||
except ValueError, exc:
|
|
||||||
self.assertEqual(str(exc), "time zone offset -4542 is not a "
|
|
||||||
"whole number of minutes")
|
|
||||||
else:
|
|
||||||
self.fail("Expected ValueError")
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user