Repository: boto/botocore
Issue: RefreshableCredentials serializing without timezone info

Hey boto folks -

I'm currently implementing my own CredentialsProvider that provides RefreshableCredentials. I'm having issues with code that has been working, but recently broke. I have to wonder if either one of my dependencies got bumped (although I can't prove it) or because we just entered daylight savings.

Basically, I'm always hitting this condition: 

> Credentials were refreshed, but the refreshed credentials are still expired.

To create my meta-data, I'm using the same(?) code used in botocore:

```
from datetime import datetime, timedelta
from dateutil.tz import tzlocal
expiration = datetime.now(tzlocal()) + timedelta(minutes=5)
expiration.strftime('%Y-%m-%dT%H:%M:%SZ')
```

> '2016-04-07T10:26:04Z'

This doesn't encode my local time zone information, as %Z is needed.

I can demonstrate this inconsistency using some of the internal boto funcs

```
from botocore.credentials import _local_now, _serialize_if_needed, _parse_if_needed
now = _local_now()
print now
serialized = _serialize_if_needed(now)
print serialized
deserialized = _parse_if_needed(serialized)
print deserialized
print now - deserialized
```

> 2016-04-07 10:24:12.336556-04:00
> 2016-04-07T10:24:12Z
> 2016-04-07 10:24:12+00:00
> 4:00:00.336556

I can fix this issue on my end by encoding the timezone information in my format string `'%Y-%m-%dT%H:%M:%S%Z'`, but I'm convinced botocore should do the same in its default time serialization code

TL;DR
It seems like your `time_fetcher` is using a timezone aware (tzlocal) datetime, but you're losing the timezone information upon serialization, defaulting to UTC. So in my case, it's comparing EST to UTC when determining expiration time.


Changed files:
tests/unit/test_credentials.py, botocore/credentials.py

diff --git a/botocore/credentials.py b/botocore/credentials.py
index 76fa52f29..f5f6ff5a1 100644
--- a/botocore/credentials.py
+++ b/botocore/credentials.py
@@ -121,7 +121,7 @@ def _parse_if_needed(value):
 
 def _serialize_if_needed(value):
     if isinstance(value, datetime.datetime):
-        return value.strftime('%Y-%m-%dT%H:%M:%SZ')
+        return value.strftime('%Y-%m-%dT%H:%M:%S%Z')
     return value
 
 
diff --git a/tests/unit/test_credentials.py b/tests/unit/test_credentials.py
index 76a90e84d..1ca586997 100644
--- a/tests/unit/test_credentials.py
+++ b/tests/unit/test_credentials.py
@@ -15,10 +15,10 @@ from datetime import datetime, timedelta
 import mock
 import os
 
-from dateutil.tz import tzlocal
+from dateutil.tz import tzlocal, tzutc
 
 from botocore import credentials
-from botocore.credentials import EnvProvider
+from botocore.credentials import EnvProvider, create_assume_role_refresher
 import botocore.exceptions
 import botocore.session
 from tests import unittest, BaseEnvVar, IntegerRefresher
@@ -746,6 +746,23 @@ class TestAssumeRoleCredentialProvider(unittest.TestCase):
         self.assertEqual(creds.secret_key, 'bar')
         self.assertEqual(creds.token, 'baz')
 
+    def test_assume_role_refresher_serializes_datetime(self):
+        client = mock.Mock()
+        time_zone = tzutc()
+        expiration = datetime(
+            year=2016, month=11, day=6, hour=1, minute=30, tzinfo=time_zone)
+        client.assume_role.return_value = {
+            'Credentials': {
+                'AccessKeyId': 'foo',
+                'SecretAccessKey': 'bar',
+                'SessionToken': 'baz',
+                'Expiration': expiration,
+            }
+        }
+        refresh = create_assume_role_refresher(client, {})
+        expiry_time = refresh()['expiry_time']
+        self.assertEqual(expiry_time, '2016-11-06T01:30:00UTC')
+
     def test_assume_role_retrieves_from_cache(self):
         date_in_future = datetime.utcnow() + timedelta(seconds=1000)
         utc_timestamp = date_in_future.isoformat() + 'Z'