Repository: sdispater/pendulum
Pull request #93 (2017-02-01 23:25:48)

Add support for stdlib datetime.timezone instances
I mistakenly opened magicstack/asyncpg#71 thinking the issue was on their side; it's actually a bug with how Pendulum imports foreign timezones. Running `astimezone(z)` on a `pendulum` instance where `z` is some instance of `datetime.timezone` will cause the following `ValueError`:

```python
Traceback (most recent call last):
  File "/opt/spoton_venvs/inventory/lib/python3.5/site-packages/aiohttp/web_server.py", line 61, in handle_request
    resp = yield from self._handler(request)
  File "/opt/spoton_venvs/inventory/lib/python3.5/site-packages/aiohttp/web.py", line 249, in _handle
    resp = yield from handler(request)
  File "main.py", line 117, in api_configs_post
    await con.execute(INSERT_CONFIG, *args[0])
  File "/opt/spoton_venvs/inventory/lib/python3.5/site-packages/asyncpg/connection.py", line 174, in execute
    True, timeout)
  File "asyncpg/protocol/protocol.pyx", line 157, in bind_execute (asyncpg/protocol/protocol.c:55559)
  File "asyncpg/protocol/prepared_stmt.pyx", line 122, in asyncpg.protocol.protocol.PreparedStatementState._encode_bind_msg (asyncpg/protocol/protocol.c:51680)
  File "asyncpg/protocol/codecs/base.pyx", line 134, in asyncpg.protocol.protocol.Codec.encode (asyncpg/protocol/protocol.c:13503)
  File "asyncpg/protocol/codecs/base.pyx", line 86, in asyncpg.protocol.protocol.Codec.encode_scalar (asyncpg/protocol/protocol.c:12943)
  File "asyncpg/protocol/codecs/datetime.pyx", line 156, in asyncpg.protocol.protocol.timestamptz_encode (asyncpg/protocol/protocol.c:23017)
  File "/opt/spoton_venvs/inventory/lib/python3.5/site-packages/pendulum/pendulum.py", line 1921, in astimezone
    tz = self._safe_create_datetime_zone(tz)
  File "/opt/spoton_venvs/inventory/lib/python3.5/site-packages/pendulum/pendulum.py", line 78, in _safe_create_datetime_zone
    raise ValueError('Unsupported timezone {}'.format(obj))
ValueError: Unsupported timezone UTC+00:00
```

This pull request resolves this issue and includes a unit test to prove it - checking out `master` for path `pendulum/pendulum.py` will cause the newly added test to fail with a traceback similar to the above.


Patch:
diff --git a/pendulum/pendulum.py b/pendulum/pendulum.py
index 48ec149..e9e6e61 100644
--- a/pendulum/pendulum.py
+++ b/pendulum/pendulum.py
@@ -70,16 +70,11 @@ class Pendulum(Date, datetime.datetime):
         elif isinstance(obj, datetime.tzinfo) and not isinstance(obj, Timezone):
             # pytz
             if hasattr(obj, 'localize'):
-                obj = obj.zone
-            else:
-                # We have no sure way to figure out
-                # the timezone name, we raise an error
-
-                raise ValueError('Unsupported timezone {}'.format(obj))
+                return cls._timezone(obj.zone)
 
-        tz = cls._timezone(obj)
+            return FixedTimezone(obj.utcoffset(None).total_seconds())
 
-        return tz
+        return cls._timezone(obj)
 
     @classmethod
     def _local_timezone(cls):


Test patch:
diff --git a/tests/pendulum_tests/test_timezone.py b/tests/pendulum_tests/test_timezone.py
index 5c3d60f..2971493 100644
--- a/tests/pendulum_tests/test_timezone.py
+++ b/tests/pendulum_tests/test_timezone.py
@@ -1,9 +1,13 @@
 # -*- coding: utf-8 -*-
 
+import sys
 from pendulum import Pendulum
 
 from .. import AbstractTestCase
 
+if sys.version_info >= (3, 2):
+    from datetime import timedelta, timezone
+
 
 class TimezoneTest(AbstractTestCase):
 
@@ -36,3 +40,12 @@ class TimezoneTest(AbstractTestCase):
         d = d.astimezone('Europe/Paris')
         self.assertEqual('Europe/Paris', d.timezone_name)
         self.assertPendulum(d, now.year, now.month, now.day, now.hour + 1, now.minute)
+
+        if sys.version_info >= (3, 2):
+            d = d.astimezone(timezone.utc)
+            self.assertEqual('+00:00', d.timezone_name)
+            self.assertPendulum(d, now.year, now.month, now.day, now.hour, now.minute)
+
+            d = d.astimezone(timezone(timedelta(hours=-8)))
+            self.assertEqual('-08:00', d.timezone_name)
+            self.assertPendulum(d, now.year, now.month, now.day, now.hour - 8, now.minute)