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

Reply via email to