Skip to content

Commit 0a2741a

Browse files
committed
Consolidate parsing of RFC 3339 datetime strings.
Adding helpers for conversion both to and from strings. Fixes #1146.
1 parent f220a36 commit 0a2741a

File tree

8 files changed

+93
-31
lines changed

8 files changed

+93
-31
lines changed

gcloud/_helpers.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,31 @@ def _total_seconds(offset):
298298
return offset.total_seconds()
299299

300300

301+
def _str_to_datetime(dt_str):
302+
"""Convert a string to a native timestamp.
303+
304+
:type dt_str: str
305+
:param dt_str: The string to convert.
306+
307+
:rtype: :class:`datetime.datetime`
308+
:returns: The datetime object created from the string.
309+
"""
310+
return datetime.datetime.strptime(
311+
dt_str, _RFC3339_MICROS).replace(tzinfo=UTC)
312+
313+
314+
def _datetime_to_str(value):
315+
"""Convert a native timestamp to a string.
316+
317+
:type value: :class:`datetime.datetime`
318+
:param value: The datetime object to be converted to a string.
319+
320+
:rtype: str
321+
:returns: The string representing the datetime stamp.
322+
"""
323+
return value.strftime(_RFC3339_MICROS)
324+
325+
301326
def _to_bytes(value, encoding='ascii'):
302327
"""Converts a string value to bytes, if necessary.
303328

gcloud/dns/changes.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,9 @@
1414

1515
"""Define API ResourceRecordSets."""
1616

17-
import datetime
18-
1917
import six
2018

21-
from gcloud._helpers import UTC
22-
from gcloud._helpers import _RFC3339_MICROS
19+
from gcloud._helpers import _str_to_datetime
2320
from gcloud.exceptions import NotFound
2421
from gcloud.dns.resource_record_set import ResourceRecordSet
2522

@@ -121,8 +118,7 @@ def started(self):
121118
"""
122119
stamp = self._properties.get('startTime')
123120
if stamp is not None:
124-
return datetime.datetime.strptime(stamp, _RFC3339_MICROS).replace(
125-
tzinfo=UTC)
121+
return _str_to_datetime(stamp)
126122

127123
@property
128124
def additions(self):

gcloud/pubsub/message.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
"""Define API Topics."""
1616

1717
import base64
18-
import datetime
1918

20-
from gcloud._helpers import _RFC3339_MICROS
21-
from gcloud._helpers import UTC
19+
from gcloud._helpers import _str_to_datetime
2220

2321

2422
class Message(object):
@@ -56,16 +54,15 @@ def timestamp(self):
5654
Allows sorting messages in publication order (assuming consistent
5755
clocks across all publishers).
5856
59-
:rtype: datetime
57+
:rtype: :class:`datetime.datetime`
6058
:returns: timestamp (in UTC timezone) parsed from RFC 3339 timestamp
6159
:raises: ValueError if timestamp not in ``attributes``, or if it does
6260
not match the RFC 3339 format.
6361
"""
6462
stamp = self.attributes.get('timestamp')
6563
if stamp is None:
6664
raise ValueError('No timestamp')
67-
return datetime.datetime.strptime(stamp, _RFC3339_MICROS).replace(
68-
tzinfo=UTC)
65+
return _str_to_datetime(stamp)
6966

7067
@classmethod
7168
def from_api_repr(cls, api_repr):

gcloud/pubsub/topic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
import base64
1818

19+
from gcloud._helpers import _datetime_to_str
1920
from gcloud._helpers import _NOW
20-
from gcloud._helpers import _RFC3339_MICROS
2121
from gcloud.exceptions import NotFound
2222
from gcloud.pubsub._helpers import topic_name_from_path
2323
from gcloud.pubsub.subscription import Subscription
@@ -155,7 +155,7 @@ def _timestamp_message(self, attrs):
155155
Helper method for ``publish``/``Batch.publish``.
156156
"""
157157
if self.timestamp_messages and 'timestamp' not in attrs:
158-
attrs['timestamp'] = _NOW().strftime(_RFC3339_MICROS)
158+
attrs['timestamp'] = _datetime_to_str(_NOW())
159159

160160
def publish(self, message, client=None, **attrs):
161161
"""API call: publish a message to a topic via a POST request

gcloud/search/document.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919
import six
2020

21-
from gcloud._helpers import UTC
22-
from gcloud._helpers import _RFC3339_MICROS
21+
from gcloud._helpers import _datetime_to_str
22+
from gcloud._helpers import _str_to_datetime
2323
from gcloud.exceptions import NotFound
2424

2525

@@ -200,8 +200,7 @@ def _parse_value_resource(resource):
200200
return NumberValue(value)
201201
if 'timestampValue' in resource:
202202
stamp = resource['timestampValue']
203-
value = datetime.datetime.strptime(stamp, _RFC3339_MICROS)
204-
value = value.replace(tzinfo=UTC)
203+
value = _str_to_datetime(stamp)
205204
return TimestampValue(value)
206205
if 'geoValue' in resource:
207206
lat_long = resource['geoValue']
@@ -259,7 +258,7 @@ def _build_value_resource(value):
259258
elif value.value_type == 'number':
260259
result['numberValue'] = value.number_value
261260
elif value.value_type == 'timestamp':
262-
stamp = value.timestamp_value.strftime(_RFC3339_MICROS)
261+
stamp = _datetime_to_str(value.timestamp_value)
263262
result['timestampValue'] = stamp
264263
elif value.value_type == 'geo':
265264
result['geoValue'] = '%s, %s' % value.geo_value

gcloud/storage/blob.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
"""Create / interact with Google Cloud Storage blobs."""
1616

1717
import copy
18-
import datetime
1918
from io import BytesIO
2019
import json
2120
import mimetypes
@@ -25,8 +24,7 @@
2524
import six
2625
from six.moves.urllib.parse import quote # pylint: disable=F0401
2726

28-
from gcloud._helpers import _RFC3339_MICROS
29-
from gcloud._helpers import UTC
27+
from gcloud._helpers import _str_to_datetime
3028
from gcloud.credentials import generate_signed_url
3129
from gcloud.exceptions import NotFound
3230
from gcloud.storage._helpers import _PropertyMixin
@@ -747,8 +745,7 @@ def time_deleted(self):
747745
"""
748746
value = self._properties.get('timeDeleted')
749747
if value is not None:
750-
naive = datetime.datetime.strptime(value, _RFC3339_MICROS)
751-
return naive.replace(tzinfo=UTC)
748+
return _str_to_datetime(value)
752749

753750
@property
754751
def updated(self):
@@ -762,8 +759,7 @@ def updated(self):
762759
"""
763760
value = self._properties.get('updated')
764761
if value is not None:
765-
naive = datetime.datetime.strptime(value, _RFC3339_MICROS)
766-
return naive.replace(tzinfo=UTC)
762+
return _str_to_datetime(value)
767763

768764

769765
class _UploadConfig(object):

gcloud/storage/bucket.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,11 @@
1414

1515
"""Create / interact with gcloud storage buckets."""
1616

17-
import datetime
1817
import copy
1918

2019
import six
2120

22-
from gcloud._helpers import _RFC3339_MICROS
23-
from gcloud._helpers import UTC
21+
from gcloud._helpers import _str_to_datetime
2422
from gcloud.exceptions import NotFound
2523
from gcloud.iterator import Iterator
2624
from gcloud.storage._helpers import _PropertyMixin
@@ -705,8 +703,7 @@ def time_created(self):
705703
"""
706704
value = self._properties.get('timeCreated')
707705
if value is not None:
708-
naive = datetime.datetime.strptime(value, _RFC3339_MICROS)
709-
return naive.replace(tzinfo=UTC)
706+
return _str_to_datetime(value)
710707

711708
@property
712709
def versioning_enabled(self):

gcloud/test__helpers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,58 @@ def test_it(self):
405405
self.assertEqual(result, 1.414)
406406

407407

408+
class Test__str_to_datetime(unittest2.TestCase):
409+
410+
def _callFUT(self, dt_str):
411+
from gcloud._helpers import _str_to_datetime
412+
return _str_to_datetime(dt_str)
413+
414+
def test_it(self):
415+
import datetime
416+
from gcloud._helpers import UTC
417+
418+
year = 2009
419+
month = 12
420+
day = 17
421+
hour = 12
422+
minute = 44
423+
seconds = 32
424+
micros = 123456
425+
426+
dt_str = '%d-%02d-%02dT%02d:%02d:%02d.%06dZ' % (
427+
year, month, day, hour, minute, seconds, micros)
428+
result = self._callFUT(dt_str)
429+
expected_result = datetime.datetime(
430+
year, month, day, hour, minute, seconds, micros, UTC)
431+
self.assertEqual(result, expected_result)
432+
433+
434+
class Test__datetime_to_str(unittest2.TestCase):
435+
436+
def _callFUT(self, value):
437+
from gcloud._helpers import _datetime_to_str
438+
return _datetime_to_str(value)
439+
440+
def test_it(self):
441+
import datetime
442+
from gcloud._helpers import UTC
443+
444+
year = 2009
445+
month = 12
446+
day = 17
447+
hour = 12
448+
minute = 44
449+
seconds = 32
450+
micros = 123456
451+
452+
to_convert = datetime.datetime(
453+
year, month, day, hour, minute, seconds, micros, UTC)
454+
dt_str = '%d-%02d-%02dT%02d:%02d:%02d.%06dZ' % (
455+
year, month, day, hour, minute, seconds, micros)
456+
result = self._callFUT(to_convert)
457+
self.assertEqual(result, dt_str)
458+
459+
408460
class Test__to_bytes(unittest2.TestCase):
409461

410462
def _callFUT(self, *args, **kwargs):

0 commit comments

Comments
 (0)