Repository: boto/botocore (python)

Issue:
serialize._convert_header_value produces incorrect timestamp value when local timezone is not UTC
Current function definition:
```python
    def _convert_header_value(self, shape, value):
        if shape.type_name == 'timestamp':
            datetime_obj = parse_timestamp(value)
            timestamp = time.mktime(datetime_obj.timetuple())
            return self._timestamp_rfc822(timestamp)
        else:
            return value
```

If `value` is either `iso8601` or `rfc822` string with a zone specifier, `datetime_obj` will be a `datetime` with a valid `tzinfo`. Calling `datetime_obj.timetuple()` results in a local `struct_time`, which `time.mktime` converts to a local timestamp, which when eventually passed to `email.utlils.formatdate` will result in a value skewed by a margin equal to the local tz offset.

For example (my timezone is US/Central):
```python
>>> import datetime
>>> import time
>>> import email.utils
>>> from dateutil.tz import tzutc, tzlocal
>>> datetime.datetime(2015, 6, 17, 22, 11, 58, tzinfo=tzutc())
datetime.datetime(2015, 6, 17, 22, 11, 58, tzinfo=tzutc())
>>> time.mktime(_.timetuple())
1434600718.0
>>> email.utils.formatdate(_, usegmt=True)
'Thu, 18 Jun 2015 04:11:58 GMT'
```

I noticed this problem when trying to use (in boto3) `s3.Object`'s `last_modified` attribute to determine if an object had changed since it was last retrieved like this: `s3.Object.get(InModifiedSince=o.last_modified.isoformat())`

I think the right behavior is to call `parse_to_aware_datetime`, `datetime_obj.utctimetuple` and `calendar.timegm` instead of `parse_timestamp`, `datetime_obj.timetuple` and `time.mktime` from `_convert_header_value`:
```python
    def _convert_header_value(self, shape, value):
        if shape.type_name == 'timestamp':
            datetime_obj = parse_to_aware_datetime(value)
            timestamp = calendar.timegm(datetime_obj.utctimetuple())
            return self._timestamp_rfc822(timestamp)
        else:
            return value
```

Calling `parse_to_aware_datetime` allows `value` to be a `datetime` in addition to the already supported `integer` and `string` types (would make boto3 docs correct in places where datetime is given as a parameter type).

Calling `datetime_obj.utctimetuple` does not actually change the behavior if the `parse_to_aware_datetime` change is made because `datetime_obj` will always be in utc. But, I think it expresses the expectation more clearly.

Calling `calendar.timegm` produces a UTC timestamp that `_timestamp_rfc822` will convert to a string representing the same moment in time that `datetime_obj` represented.

For example:
```python
>>> import datetime
>>> import calendar
>>> import email.utils
>>> from dateutil.tz import tzutc, tzlocal
>>> datetime.datetime(2015, 6, 17, 22, 11, 58, tzinfo=tzutc())
datetime.datetime(2015, 6, 17, 22, 11, 58, tzinfo=tzutc())
>>> calendar.timegm(_.utctimetuple())
1434579118
>>> email.utils.formatdate(_, usegmt=True)
'Wed, 17 Jun 2015 22:11:58 GMT'
```

Pull request:
BaseRestSerializer should handle timestamp headers consistently. fixes #625
I took a crack at fixing #625, adding test cases for the failing case as well as the cases that were working before.

Patch:
diff --git a/botocore/serialize.py b/botocore/serialize.py
index 0a78b956f..da37e8209 100644
--- a/botocore/serialize.py
+++ b/botocore/serialize.py
@@ -38,7 +38,6 @@ with the exception of blob types.  Those are assumed to be binary,
 and if a str/unicode type is passed in, it will be encoded as utf-8.
 """
 import re
-import time
 import base64
 from xml.etree import ElementTree
 import calendar
@@ -46,7 +45,7 @@ import calendar
 from botocore.compat import six
 
 from botocore.compat import json, formatdate
-from botocore.utils import parse_timestamp, parse_to_aware_datetime
+from botocore.utils import parse_to_aware_datetime
 from botocore.utils import percent_encode
 from botocore import validate
 
@@ -502,8 +501,8 @@ class BaseRestSerializer(Serializer):
 
     def _convert_header_value(self, shape, value):
         if shape.type_name == 'timestamp':
-            datetime_obj = parse_timestamp(value)
-            timestamp = time.mktime(datetime_obj.timetuple())
+            datetime_obj = parse_to_aware_datetime(value)
+            timestamp = calendar.timegm(datetime_obj.utctimetuple())
             return self._timestamp_rfc822(timestamp)
         else:
             return value


Test patch:
diff --git a/tests/unit/test_serialize.py b/tests/unit/test_serialize.py
index 58939a88e..800c96237 100644
--- a/tests/unit/test_serialize.py
+++ b/tests/unit/test_serialize.py
@@ -137,6 +137,78 @@ class TestBinaryTypesWithRestXML(BaseModelWithBlob):
         self.assertEqual(request['body'], body)
 
 
+class TestTimestampHeadersWithRestXML(unittest.TestCase):
+
+    def setUp(self):
+        self.model = {
+            'metadata': {'protocol': 'rest-xml', 'apiVersion': '2014-01-01'},
+            'documentation': '',
+            'operations': {
+                'TestOperation': {
+                    'name': 'TestOperation',
+                    'http': {
+                        'method': 'POST',
+                        'requestUri': '/',
+                    },
+                    'input': {'shape': 'InputShape'},
+                }
+            },
+            'shapes': {
+                'InputShape': {
+                    'type': 'structure',
+                    'members': {
+                        'TimestampHeader': {
+                            'shape': 'TimestampType',
+                            'location': 'header',
+                            'locationName': 'x-timestamp'
+                        },
+                    }
+                },
+                'TimestampType': {
+                    'type': 'timestamp',
+                }
+            }
+        }
+        self.service_model = ServiceModel(self.model)
+
+    def serialize_to_request(self, input_params):
+        request_serializer = serialize.create_serializer(
+            self.service_model.metadata['protocol'])
+        return request_serializer.serialize_to_request(
+            input_params, self.service_model.operation_model('TestOperation'))
+
+    def test_accepts_datetime_object(self):
+        request = self.serialize_to_request(
+            {'TimestampHeader': datetime.datetime(2014, 1, 1, 12, 12, 12,
+                                                  tzinfo=dateutil.tz.tzutc())})
+        self.assertEqual(request['headers']['x-timestamp'],
+                         'Wed, 01 Jan 2014 12:12:12 GMT')
+
+    def test_accepts_iso_8601_format(self):
+        request = self.serialize_to_request(
+            {'TimestampHeader': '2014-01-01T12:12:12+00:00'})
+        self.assertEqual(request['headers']['x-timestamp'],
+                         'Wed, 01 Jan 2014 12:12:12 GMT')
+
+    def test_accepts_iso_8601_format_non_utc(self):
+        request = self.serialize_to_request(
+            {'TimestampHeader': '2014-01-01T07:12:12-05:00'})
+        self.assertEqual(request['headers']['x-timestamp'],
+                         'Wed, 01 Jan 2014 12:12:12 GMT')
+
+    def test_accepts_rfc_822_format(self):
+        request = self.serialize_to_request(
+            {'TimestampHeader': 'Wed, 01 Jan 2014 12:12:12 GMT'})
+        self.assertEqual(request['headers']['x-timestamp'],
+                         'Wed, 01 Jan 2014 12:12:12 GMT')
+
+    def test_accepts_unix_timestamp_integer(self):
+        request = self.serialize_to_request(
+            {'TimestampHeader': 1388578332})
+        self.assertEqual(request['headers']['x-timestamp'],
+                         'Wed, 01 Jan 2014 12:12:12 GMT')
+
+
 class TestTimestamps(unittest.TestCase):
     def setUp(self):
         self.model = {