diff --git a/gcloud/_helpers.py b/gcloud/_helpers.py index 3a9cbe2704e4..89a98da4ad41 100644 --- a/gcloud/_helpers.py +++ b/gcloud/_helpers.py @@ -298,6 +298,31 @@ def _total_seconds(offset): return offset.total_seconds() +def _rfc3339_to_datetime(dt_str): + """Convert a string to a native timestamp. + + :type dt_str: str + :param dt_str: The string to convert. + + :rtype: :class:`datetime.datetime` + :returns: The datetime object created from the string. + """ + return datetime.datetime.strptime( + dt_str, _RFC3339_MICROS).replace(tzinfo=UTC) + + +def _datetime_to_rfc3339(value): + """Convert a native timestamp to a string. + + :type value: :class:`datetime.datetime` + :param value: The datetime object to be converted to a string. + + :rtype: str + :returns: The string representing the datetime stamp. + """ + return value.strftime(_RFC3339_MICROS) + + def _to_bytes(value, encoding='ascii'): """Converts a string value to bytes, if necessary. diff --git a/gcloud/dns/changes.py b/gcloud/dns/changes.py index 84795cba1e8e..e3e05e723397 100644 --- a/gcloud/dns/changes.py +++ b/gcloud/dns/changes.py @@ -14,12 +14,9 @@ """Define API ResourceRecordSets.""" -import datetime - import six -from gcloud._helpers import UTC -from gcloud._helpers import _RFC3339_MICROS +from gcloud._helpers import _rfc3339_to_datetime from gcloud.exceptions import NotFound from gcloud.dns.resource_record_set import ResourceRecordSet @@ -121,8 +118,7 @@ def started(self): """ stamp = self._properties.get('startTime') if stamp is not None: - return datetime.datetime.strptime(stamp, _RFC3339_MICROS).replace( - tzinfo=UTC) + return _rfc3339_to_datetime(stamp) @property def additions(self): diff --git a/gcloud/pubsub/message.py b/gcloud/pubsub/message.py index a2f8ece27452..e8db230aa7b6 100644 --- a/gcloud/pubsub/message.py +++ b/gcloud/pubsub/message.py @@ -15,10 +15,8 @@ """Define API Topics.""" import base64 -import datetime -from gcloud._helpers import _RFC3339_MICROS -from gcloud._helpers import UTC +from gcloud._helpers import _rfc3339_to_datetime class Message(object): @@ -56,7 +54,7 @@ def timestamp(self): Allows sorting messages in publication order (assuming consistent clocks across all publishers). - :rtype: datetime + :rtype: :class:`datetime.datetime` :returns: timestamp (in UTC timezone) parsed from RFC 3339 timestamp :raises: ValueError if timestamp not in ``attributes``, or if it does not match the RFC 3339 format. @@ -64,8 +62,7 @@ def timestamp(self): stamp = self.attributes.get('timestamp') if stamp is None: raise ValueError('No timestamp') - return datetime.datetime.strptime(stamp, _RFC3339_MICROS).replace( - tzinfo=UTC) + return _rfc3339_to_datetime(stamp) @classmethod def from_api_repr(cls, api_repr): diff --git a/gcloud/pubsub/topic.py b/gcloud/pubsub/topic.py index 8bb849b2d62a..c4ce30645938 100644 --- a/gcloud/pubsub/topic.py +++ b/gcloud/pubsub/topic.py @@ -16,8 +16,8 @@ import base64 +from gcloud._helpers import _datetime_to_rfc3339 from gcloud._helpers import _NOW -from gcloud._helpers import _RFC3339_MICROS from gcloud.exceptions import NotFound from gcloud.pubsub._helpers import topic_name_from_path from gcloud.pubsub.subscription import Subscription @@ -155,7 +155,7 @@ def _timestamp_message(self, attrs): Helper method for ``publish``/``Batch.publish``. """ if self.timestamp_messages and 'timestamp' not in attrs: - attrs['timestamp'] = _NOW().strftime(_RFC3339_MICROS) + attrs['timestamp'] = _datetime_to_rfc3339(_NOW()) def publish(self, message, client=None, **attrs): """API call: publish a message to a topic via a POST request diff --git a/gcloud/search/document.py b/gcloud/search/document.py index 94f122b933c8..ecbff93ba16e 100644 --- a/gcloud/search/document.py +++ b/gcloud/search/document.py @@ -18,8 +18,8 @@ import six -from gcloud._helpers import UTC -from gcloud._helpers import _RFC3339_MICROS +from gcloud._helpers import _datetime_to_rfc3339 +from gcloud._helpers import _rfc3339_to_datetime from gcloud.exceptions import NotFound @@ -200,8 +200,7 @@ def _parse_value_resource(resource): return NumberValue(value) if 'timestampValue' in resource: stamp = resource['timestampValue'] - value = datetime.datetime.strptime(stamp, _RFC3339_MICROS) - value = value.replace(tzinfo=UTC) + value = _rfc3339_to_datetime(stamp) return TimestampValue(value) if 'geoValue' in resource: lat_long = resource['geoValue'] @@ -259,7 +258,7 @@ def _build_value_resource(value): elif value.value_type == 'number': result['numberValue'] = value.number_value elif value.value_type == 'timestamp': - stamp = value.timestamp_value.strftime(_RFC3339_MICROS) + stamp = _datetime_to_rfc3339(value.timestamp_value) result['timestampValue'] = stamp elif value.value_type == 'geo': result['geoValue'] = '%s, %s' % value.geo_value diff --git a/gcloud/storage/blob.py b/gcloud/storage/blob.py index 9835df6a7f60..8a6df5c6ce8d 100644 --- a/gcloud/storage/blob.py +++ b/gcloud/storage/blob.py @@ -15,7 +15,6 @@ """Create / interact with Google Cloud Storage blobs.""" import copy -import datetime from io import BytesIO import json import mimetypes @@ -25,8 +24,7 @@ import six from six.moves.urllib.parse import quote # pylint: disable=F0401 -from gcloud._helpers import _RFC3339_MICROS -from gcloud._helpers import UTC +from gcloud._helpers import _rfc3339_to_datetime from gcloud.credentials import generate_signed_url from gcloud.exceptions import NotFound from gcloud.storage._helpers import _PropertyMixin @@ -747,8 +745,7 @@ def time_deleted(self): """ value = self._properties.get('timeDeleted') if value is not None: - naive = datetime.datetime.strptime(value, _RFC3339_MICROS) - return naive.replace(tzinfo=UTC) + return _rfc3339_to_datetime(value) @property def updated(self): @@ -762,8 +759,7 @@ def updated(self): """ value = self._properties.get('updated') if value is not None: - naive = datetime.datetime.strptime(value, _RFC3339_MICROS) - return naive.replace(tzinfo=UTC) + return _rfc3339_to_datetime(value) class _UploadConfig(object): diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py index f29b510dff1f..589f8527fb3f 100644 --- a/gcloud/storage/bucket.py +++ b/gcloud/storage/bucket.py @@ -14,13 +14,11 @@ """Create / interact with gcloud storage buckets.""" -import datetime import copy import six -from gcloud._helpers import _RFC3339_MICROS -from gcloud._helpers import UTC +from gcloud._helpers import _rfc3339_to_datetime from gcloud.exceptions import NotFound from gcloud.iterator import Iterator from gcloud.storage._helpers import _PropertyMixin @@ -705,8 +703,7 @@ def time_created(self): """ value = self._properties.get('timeCreated') if value is not None: - naive = datetime.datetime.strptime(value, _RFC3339_MICROS) - return naive.replace(tzinfo=UTC) + return _rfc3339_to_datetime(value) @property def versioning_enabled(self): diff --git a/gcloud/test__helpers.py b/gcloud/test__helpers.py index a046450b7fe0..84f6d35aa752 100644 --- a/gcloud/test__helpers.py +++ b/gcloud/test__helpers.py @@ -405,6 +405,58 @@ def test_it(self): self.assertEqual(result, 1.414) +class Test__rfc3339_to_datetime(unittest2.TestCase): + + def _callFUT(self, dt_str): + from gcloud._helpers import _rfc3339_to_datetime + return _rfc3339_to_datetime(dt_str) + + def test_it(self): + import datetime + from gcloud._helpers import UTC + + year = 2009 + month = 12 + day = 17 + hour = 12 + minute = 44 + seconds = 32 + micros = 123456 + + dt_str = '%d-%02d-%02dT%02d:%02d:%02d.%06dZ' % ( + year, month, day, hour, minute, seconds, micros) + result = self._callFUT(dt_str) + expected_result = datetime.datetime( + year, month, day, hour, minute, seconds, micros, UTC) + self.assertEqual(result, expected_result) + + +class Test__datetime_to_rfc3339(unittest2.TestCase): + + def _callFUT(self, value): + from gcloud._helpers import _datetime_to_rfc3339 + return _datetime_to_rfc3339(value) + + def test_it(self): + import datetime + from gcloud._helpers import UTC + + year = 2009 + month = 12 + day = 17 + hour = 12 + minute = 44 + seconds = 32 + micros = 123456 + + to_convert = datetime.datetime( + year, month, day, hour, minute, seconds, micros, UTC) + dt_str = '%d-%02d-%02dT%02d:%02d:%02d.%06dZ' % ( + year, month, day, hour, minute, seconds, micros) + result = self._callFUT(to_convert) + self.assertEqual(result, dt_str) + + class Test__to_bytes(unittest2.TestCase): def _callFUT(self, *args, **kwargs):