Skip to content

Commit

Permalink
Consolidate parsing of RFC 3339 datetime strings.
Browse files Browse the repository at this point in the history
Adding helpers for conversion both to and from strings.
Fixes googleapis#1146.
  • Loading branch information
dhermes committed Jan 7, 2016
1 parent f284923 commit 1080401
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 31 deletions.
25 changes: 25 additions & 0 deletions gcloud/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 2 additions & 6 deletions gcloud/dns/changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand Down
9 changes: 3 additions & 6 deletions gcloud/pubsub/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -56,16 +54,15 @@ 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.
"""
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):
Expand Down
4 changes: 2 additions & 2 deletions gcloud/pubsub/topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions gcloud/search/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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
Expand Down
10 changes: 3 additions & 7 deletions gcloud/storage/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"""Create / interact with Google Cloud Storage blobs."""

import copy
import datetime
from io import BytesIO
import json
import mimetypes
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down
7 changes: 2 additions & 5 deletions gcloud/storage/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
52 changes: 52 additions & 0 deletions gcloud/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit 1080401

Please sign in to comment.