Author: cito
Date: Tue Feb 9 08:14:26 2016
New Revision: 849
Log:
Make timetz and timestamptz work properly with Python 2
Python 2 has no concrete timezone class, therefore we had so far returned only
naive datetimes in this case. This patch adds a simple concrete timezone class,
so we can return timetz and timestamptz with timezones in Python 2 as well.
Modified:
trunk/pg.py
trunk/pgdb.py
trunk/tests/test_classic_dbwrapper.py
trunk/tests/test_dbapi20.py
Modified: trunk/pg.py
==============================================================================
--- trunk/pg.py Tue Feb 9 06:00:38 2016 (r848)
+++ trunk/pg.py Tue Feb 9 08:14:26 2016 (r849)
@@ -27,7 +27,7 @@
# both that copyright notice and this permission notice appear in
# supporting documentation.
-from __future__ import print_function
+from __future__ import print_function, division
from _pg import *
@@ -36,7 +36,7 @@
import select
import warnings
-from datetime import date, time, datetime, timedelta
+from datetime import date, time, datetime, timedelta, tzinfo
from decimal import Decimal
from math import isnan, isinf
from collections import namedtuple
@@ -157,15 +157,59 @@
return list(signature(func).parameters)
try:
- if datetime.strptime('+0100', '%z') is None:
- raise ValueError
-except ValueError: # Python < 3.2
- timezones = None
+ from datetime import timezone
+except ImportError: # Python < 3.2
+
+ class timezone(tzinfo):
+ """Simple timezone implementation."""
+
+ def __init__(self, offset, name=None):
+ self.offset = offset
+ if not name:
+ minutes = self.offset.days * 1440 + self.offset.seconds // 60
+ if minutes < 0:
+ hours, minutes = divmod(-minutes, 60)
+ hours = -hours
+ else:
+ hours, minutes = divmod(minutes, 60)
+ name = 'UTC%+03d:%02d' % (hours, minutes)
+ self.name = name
+
+ def utcoffset(self, dt):
+ return self.offset
+
+ def tzname(self, dt):
+ return self.name
+
+ def dst(self, dt):
+ return None
+
+ timezone.utc = timezone(timedelta(0), 'UTC')
+
+ _has_timezone = False
else:
- # time zones used in Postgres timestamptz output
- timezones = dict(CET='+0100', EET='+0200', EST='-0500',
- GMT='+0000', HST='-1000', MET='+0100', MST='-0700',
- UCT='+0000', UTC='+0000', WET='+0000')
+ _has_timezone = True
+
+# time zones used in Postgres timestamptz output
+_timezones = dict(CET='+0100', EET='+0200', EST='-0500',
+ GMT='+0000', HST='-1000', MET='+0100', MST='-0700',
+ UCT='+0000', UTC='+0000', WET='+0000')
+
+
+def _timezone_as_offset(tz):
+ if tz.startswith(('+', '-')):
+ if len(tz) < 5:
+ return tz + '00'
+ return tz.replace(':', '')
+ return _timezones.get(tz, '+0000')
+
+
+def _get_timezone(tz):
+ tz = _timezone_as_offset(tz)
+ minutes = 60 * int(tz[1:3]) + int(tz[3:5])
+ if tz[0] == '-':
+ minutes = -minutes
+ return timezone(timedelta(minutes=minutes), tz)
def _oid_key(table):
@@ -673,19 +717,12 @@
else:
tz = '+0000'
fmt = '%H:%M:%S.%f' if len(value) > 8 else '%H:%M:%S'
- if timezones:
- if tz.startswith(('+', '-')):
- if len(tz) < 5:
- tz += '00'
- else:
- tz = tz.replace(':', '')
- elif tz in timezones:
- tz = timezones[tz]
- else:
- tz = '+0000'
- value += tz
+ if _has_timezone:
+ value += _timezone_as_offset(tz)
fmt += '%z'
- return datetime.strptime(value, fmt).timetz()
+ return datetime.strptime(value, fmt).timetz()
+ return datetime.strptime(value, fmt).timetz().replace(
+ tzinfo=_get_timezone(tz))
def cast_timestamp(value, connection):
@@ -740,19 +777,13 @@
if len(value[0]) > 10:
return datetime.max
fmt = [fmt, '%H:%M:%S.%f' if len(value[1]) > 8 else '%H:%M:%S']
- if timezones:
- if tz.startswith(('+', '-')):
- if len(tz) < 5:
- tz += '00'
- else:
- tz = tz.replace(':', '')
- elif tz in timezones:
- tz = timezones[tz]
- else:
- tz = '+0000'
- value.append(tz)
+ if _has_timezone:
+ value.append(_timezone_as_offset(tz))
fmt.append('%z')
- return datetime.strptime(' '.join(value), ' '.join(fmt))
+ return datetime.strptime(' '.join(value), ' '.join(fmt))
+ return datetime.strptime(' '.join(value), ' '.join(fmt)).replace(
+ tzinfo=_get_timezone(tz))
+
_re_interval_sql_standard = regex(
'(?:([+-])?([0-9]+)-([0-9]+) ?)?'
Modified: trunk/pgdb.py
==============================================================================
--- trunk/pgdb.py Tue Feb 9 06:00:38 2016 (r848)
+++ trunk/pgdb.py Tue Feb 9 08:14:26 2016 (r849)
@@ -63,13 +63,13 @@
connection.close() # close the connection
"""
-from __future__ import print_function
+from __future__ import print_function, division
from _pg import *
__version__ = version
-from datetime import date, time, datetime, timedelta
+from datetime import date, time, datetime, timedelta, tzinfo
from time import localtime
from decimal import Decimal
from uuid import UUID as Uuid
@@ -128,15 +128,59 @@
return list(signature(func).parameters)
try:
- if datetime.strptime('+0100', '%z') is None:
- raise ValueError
-except ValueError: # Python < 3.2
- timezones = None
+ from datetime import timezone
+except ImportError: # Python < 3.2
+
+ class timezone(tzinfo):
+ """Simple timezone implementation."""
+
+ def __init__(self, offset, name=None):
+ self.offset = offset
+ if not name:
+ minutes = self.offset.days * 1440 + self.offset.seconds // 60
+ if minutes < 0:
+ hours, minutes = divmod(-minutes, 60)
+ hours = -hours
+ else:
+ hours, minutes = divmod(minutes, 60)
+ name = 'UTC%+03d:%02d' % (hours, minutes)
+ self.name = name
+
+ def utcoffset(self, dt):
+ return self.offset
+
+ def tzname(self, dt):
+ return self.name
+
+ def dst(self, dt):
+ return None
+
+ timezone.utc = timezone(timedelta(0), 'UTC')
+
+ _has_timezone = False
else:
- # time zones used in Postgres timestamptz output
- timezones = dict(CET='+0100', EET='+0200', EST='-0500',
- GMT='+0000', HST='-1000', MET='+0100', MST='-0700',
- UCT='+0000', UTC='+0000', WET='+0000')
+ _has_timezone = True
+
+# time zones used in Postgres timestamptz output
+_timezones = dict(CET='+0100', EET='+0200', EST='-0500',
+ GMT='+0000', HST='-1000', MET='+0100', MST='-0700',
+ UCT='+0000', UTC='+0000', WET='+0000')
+
+
+def _timezone_as_offset(tz):
+ if tz.startswith(('+', '-')):
+ if len(tz) < 5:
+ return tz + '00'
+ return tz.replace(':', '')
+ return _timezones.get(tz, '+0000')
+
+
+def _get_timezone(tz):
+ tz = _timezone_as_offset(tz)
+ minutes = 60 * int(tz[1:3]) + int(tz[3:5])
+ if tz[0] == '-':
+ minutes = -minutes
+ return timezone(timedelta(minutes=minutes), tz)
def decimal_type(decimal_type=None):
@@ -207,19 +251,12 @@
else:
tz = '+0000'
fmt = '%H:%M:%S.%f' if len(value) > 8 else '%H:%M:%S'
- if timezones:
- if tz.startswith(('+', '-')):
- if len(tz) < 5:
- tz += '00'
- else:
- tz = tz.replace(':', '')
- elif tz in timezones:
- tz = timezones[tz]
- else:
- tz = '+0000'
- value += tz
+ if _has_timezone:
+ value += _timezone_as_offset(tz)
fmt += '%z'
- return datetime.strptime(value, fmt).timetz()
+ return datetime.strptime(value, fmt).timetz()
+ return datetime.strptime(value, fmt).timetz().replace(
+ tzinfo=_get_timezone(tz))
def cast_timestamp(value, connection):
@@ -274,19 +311,13 @@
if len(value[0]) > 10:
return datetime.max
fmt = [fmt, '%H:%M:%S.%f' if len(value[1]) > 8 else '%H:%M:%S']
- if timezones:
- if tz.startswith(('+', '-')):
- if len(tz) < 5:
- tz += '00'
- else:
- tz = tz.replace(':', '')
- elif tz in timezones:
- tz = timezones[tz]
- else:
- tz = '+0000'
- value.append(tz)
+ if _has_timezone:
+ value.append(_timezone_as_offset(tz))
fmt.append('%z')
- return datetime.strptime(' '.join(value), ' '.join(fmt))
+ return datetime.strptime(' '.join(value), ' '.join(fmt))
+ return datetime.strptime(' '.join(value), ' '.join(fmt)).replace(
+ tzinfo=_get_timezone(tz))
+
_re_interval_sql_standard = regex(
'(?:([+-])?([0-9]+)-([0-9]+) ?)?'
Modified: trunk/tests/test_classic_dbwrapper.py
==============================================================================
--- trunk/tests/test_classic_dbwrapper.py Tue Feb 9 06:00:38 2016
(r848)
+++ trunk/tests/test_classic_dbwrapper.py Tue Feb 9 08:14:26 2016
(r849)
@@ -3572,11 +3572,11 @@
query = self.db.query
timezones = dict(CET=1, EET=2, EST=-5, UTC=0)
for timezone in sorted(timezones):
- offset = timezones[timezone]
+ tz = '%+03d00' % timezones[timezone]
try:
- tzinfo = datetime.strptime('%+03d00' % offset, '%z').tzinfo
- except ValueError: # Python < 3.3
- tzinfo = None
+ tzinfo = datetime.strptime(tz, '%z').tzinfo
+ except ValueError: # Python < 3.2
+ tzinfo = pg._get_timezone(tz)
self.db.set_parameter('timezone', timezone)
d = time(15, 9, 26, tzinfo=tzinfo)
q = "select $1::timetz"
@@ -3627,11 +3627,11 @@
query = self.db.query
timezones = dict(CET=1, EET=2, EST=-5, UTC=0)
for timezone in sorted(timezones):
- offset = timezones[timezone]
+ tz = '%+03d00' % timezones[timezone]
try:
- tzinfo = datetime.strptime('%+03d00' % offset, '%z').tzinfo
- except ValueError: # Python < 3.3
- tzinfo = None
+ tzinfo = datetime.strptime(tz, '%z').tzinfo
+ except ValueError: # Python < 3.2
+ tzinfo = pg._get_timezone(tz)
self.db.set_parameter('timezone', timezone)
for datestyle in ('ISO', 'Postgres, MDY', 'Postgres, DMY',
'SQL, MDY', 'SQL, DMY', 'German'):
Modified: trunk/tests/test_dbapi20.py
==============================================================================
--- trunk/tests/test_dbapi20.py Tue Feb 9 06:00:38 2016 (r848)
+++ trunk/tests/test_dbapi20.py Tue Feb 9 08:14:26 2016 (r849)
@@ -32,11 +32,6 @@
from uuid import UUID as Uuid
try:
- from datetime import timezone
-except ImportError: # Python < 3.2
- timezone = None
-
-try:
long
except NameError: # Python >= 3.0
long = int
@@ -578,9 +573,8 @@
for n in range(3):
values = [dt.date(), dt.time(), dt,
dt.time(), dt]
- if timezone:
- values[3] = values[3].replace(tzinfo=timezone.utc)
- values[4] = values[4].replace(tzinfo=timezone.utc)
+ values[3] = values[3].replace(tzinfo=pgdb.timezone.utc)
+ values[4] = values[4].replace(tzinfo=pgdb.timezone.utc)
if n == 0: # input as objects
params = values
if n == 1: # input as text
@@ -588,7 +582,7 @@
elif n == 2: # input using type helpers
d = (dt.year, dt.month, dt.day)
t = (dt.hour, dt.minute, dt.second, dt.microsecond)
- z = (timezone.utc,) if timezone else ()
+ z = (pgdb.timezone.utc,)
params = [pgdb.Date(*d), pgdb.Time(*t),
pgdb.Timestamp(*(d + t)), pgdb.Time(*(t + z)),
pgdb.Timestamp(*(d + t + z))]
_______________________________________________
PyGreSQL mailing list
[email protected]
https://mail.vex.net/mailman/listinfo.cgi/pygresql