mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-02-17 01:20:32 +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>
|
||||
|
||||
* Release 2.2.1.
|
||||
|
|
|
@ -365,6 +365,38 @@ the connection or globally: see the function
|
|||
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
|
||||
|
||||
|
|
|
@ -134,15 +134,11 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
|||
Dprintf("typecast_PYDATETIME_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;
|
||||
}
|
||||
tzinfo = PyObject_CallFunction(tzinfo_factory, "i", tz / 60);
|
||||
a whole number of minutes, so truncate the seconds to the
|
||||
closest minute. */
|
||||
// printf("%d %d %d\n", tz, tzmin, round(tz / 60.0));
|
||||
tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
|
||||
(int)round(tz / 60.0));
|
||||
} else {
|
||||
Py_INCREF(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);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
tzinfo = PyObject_CallFunction(tzinfo_factory, "i", tz / 60);
|
||||
a whole number of minutes, so truncate the seconds to the
|
||||
closest minute. */
|
||||
tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
|
||||
(int)round(tz / 60.0));
|
||||
} else {
|
||||
Py_INCREF(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)
|
||||
# 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(str(exc), "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(str(exc), "time zone offset -4542 is not a "
|
||||
"whole number of minutes")
|
||||
else:
|
||||
self.fail("Expected ValueError")
|
||||
# offsets that are not a whole number of minutes.
|
||||
# We round the offset to the nearest minute.
|
||||
self.check_time_tz("+01:15:00", 60 * (60 + 15))
|
||||
self.check_time_tz("+01:15:29", 60 * (60 + 15))
|
||||
self.check_time_tz("+01:15:30", 60 * (60 + 16))
|
||||
self.check_time_tz("+01:15:59", 60 * (60 + 16))
|
||||
self.check_time_tz("-01:15:00", -60 * (60 + 15))
|
||||
self.check_time_tz("-01:15:29", -60 * (60 + 15))
|
||||
self.check_time_tz("-01:15:30", -60 * (60 + 16))
|
||||
self.check_time_tz("-01:15:59", -60 * (60 + 16))
|
||||
|
||||
def check_datetime_tz(self, str_offset, offset):
|
||||
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)
|
||||
# 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(str(exc), "time zone offset 4542 is not a "
|
||||
"whole number of minutes")
|
||||
else:
|
||||
self.fail("Expected ValueError")
|
||||
|
||||
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")
|
||||
# offsets that are not a whole number of minutes.
|
||||
# We round the offset to the nearest minute.
|
||||
self.check_datetime_tz("+01:15:00", 60 * (60 + 15))
|
||||
self.check_datetime_tz("+01:15:29", 60 * (60 + 15))
|
||||
self.check_datetime_tz("+01:15:30", 60 * (60 + 16))
|
||||
self.check_datetime_tz("+01:15:59", 60 * (60 + 16))
|
||||
self.check_datetime_tz("-01:15:00", -60 * (60 + 15))
|
||||
self.check_datetime_tz("-01:15:29", -60 * (60 + 15))
|
||||
self.check_datetime_tz("-01:15:30", -60 * (60 + 16))
|
||||
self.check_datetime_tz("-01:15:59", -60 * (60 + 16))
|
||||
|
||||
def test_parse_time_no_timezone(self):
|
||||
self.assertEqual(self.TIME("13:30:29", self.curs).tzinfo, None)
|
||||
|
|
Loading…
Reference in New Issue
Block a user