From 649a67415455690f02fd2a82d899b7465b167317 Mon Sep 17 00:00:00 2001 From: Supriya Garg Date: Mon, 1 Aug 2016 18:13:34 -0400 Subject: [PATCH 1/2] Update the helper _datetime_to_rfc3339 to handle time zones. The helper function _format_timestamp in gcloud.monitoring.query is superseded by this updated helper function. --- gcloud/_helpers.py | 12 +++++-- gcloud/monitoring/query.py | 25 ++++---------- gcloud/monitoring/test_client.py | 5 +-- gcloud/monitoring/test_query.py | 47 +++++++++------------------ gcloud/test__helpers.py | 56 +++++++++++++++++++++++--------- 5 files changed, 75 insertions(+), 70 deletions(-) diff --git a/gcloud/_helpers.py b/gcloud/_helpers.py index a2c4289c1e59..58729bab73c9 100644 --- a/gcloud/_helpers.py +++ b/gcloud/_helpers.py @@ -409,15 +409,23 @@ def _rfc3339_nanos_to_datetime(dt_str): return bare_seconds.replace(microsecond=micros, tzinfo=UTC) -def _datetime_to_rfc3339(value): - """Convert a native timestamp to a string. +def _datetime_to_rfc3339(value, ignore_zone=True): + """Convert a timestamp to a string. :type value: :class:`datetime.datetime` :param value: The datetime object to be converted to a string. + :type ignore_zone: boolean + :param ignore_zone: If True, then the timezone (if any) of the datetime + object is ignored. + :rtype: str :returns: The string representing the datetime stamp. """ + if not(ignore_zone or value.tzinfo is None): + # Convert to UTC and remove the time zone info. + value = value.replace(tzinfo=None) - value.utcoffset() + return value.strftime(_RFC3339_MICROS) diff --git a/gcloud/monitoring/query.py b/gcloud/monitoring/query.py index 372959f53e9f..4e2d7d62cfa2 100644 --- a/gcloud/monitoring/query.py +++ b/gcloud/monitoring/query.py @@ -25,6 +25,7 @@ import six +from gcloud._helpers import _datetime_to_rfc3339 from gcloud.monitoring._dataframe import _build_dataframe from gcloud.monitoring.timeseries import TimeSeries @@ -498,9 +499,12 @@ def _build_query_params(self, headers_only=False, """ yield 'filter', self.filter - yield 'interval.endTime', _format_timestamp(self._end_time) + yield 'interval.endTime', _datetime_to_rfc3339( + self._end_time, ignore_zone=False) + if self._start_time is not None: - yield 'interval.startTime', _format_timestamp(self._start_time) + yield 'interval.startTime', _datetime_to_rfc3339( + self._start_time, ignore_zone=False) if self._per_series_aligner is not None: yield 'aggregation.perSeriesAligner', self._per_series_aligner @@ -651,20 +655,3 @@ def _build_label_filter(category, *args, **kwargs): terms.append(term.format(key=key, value=value)) return ' AND '.join(sorted(terms)) - - -def _format_timestamp(timestamp): - """Convert a datetime object to a string as required by the API. - - :type timestamp: :class:`datetime.datetime` - :param timestamp: A datetime object. - - :rtype: string - :returns: The formatted timestamp. For example: - ``"2016-02-17T19:18:01.763000Z"`` - """ - if timestamp.tzinfo is not None: - # Convert to UTC and remove the time zone info. - timestamp = timestamp.replace(tzinfo=None) - timestamp.utcoffset() - - return timestamp.isoformat() + 'Z' diff --git a/gcloud/monitoring/test_client.py b/gcloud/monitoring/test_client.py index 559b828f9eb4..6124eda7806d 100644 --- a/gcloud/monitoring/test_client.py +++ b/gcloud/monitoring/test_client.py @@ -28,6 +28,7 @@ def _makeOne(self, *args, **kwargs): def test_query(self): import datetime + from gcloud._helpers import _datetime_to_rfc3339 from gcloud.exceptions import NotFound START_TIME = datetime.datetime(2016, 4, 6, 22, 5, 0) @@ -119,8 +120,8 @@ def P(timestamp, value): 'path': '/projects/{project}/timeSeries/'.format(project=PROJECT), 'query_params': [ ('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)), - ('interval.endTime', END_TIME.isoformat() + 'Z'), - ('interval.startTime', START_TIME.isoformat() + 'Z'), + ('interval.endTime', _datetime_to_rfc3339(END_TIME)), + ('interval.startTime', _datetime_to_rfc3339(START_TIME)), ], } diff --git a/gcloud/monitoring/test_query.py b/gcloud/monitoring/test_query.py index 5a0f187c0c00..0d5488df6462 100644 --- a/gcloud/monitoring/test_query.py +++ b/gcloud/monitoring/test_query.py @@ -80,6 +80,11 @@ def _getTargetClass(self): def _makeOne(self, *args, **kwargs): return self._getTargetClass()(*args, **kwargs) + @staticmethod + def _make_timestamp(value): + from gcloud._helpers import _datetime_to_rfc3339 + return _datetime_to_rfc3339(value) + def test_constructor_minimal(self): client = _Client(project=PROJECT, connection=_Connection()) query = self._makeOne(client) @@ -217,7 +222,7 @@ def test_request_parameters_minimal(self): actual = list(query._build_query_params()) expected = [ ('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)), - ('interval.endTime', T1.isoformat() + 'Z'), + ('interval.endTime', self._make_timestamp(T1)), ] self.assertEqual(actual, expected) @@ -246,8 +251,8 @@ def test_request_parameters_maximal(self): page_token=PAGE_TOKEN)) expected = [ ('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)), - ('interval.endTime', T1.isoformat() + 'Z'), - ('interval.startTime', T0.isoformat() + 'Z'), + ('interval.endTime', self._make_timestamp(T1)), + ('interval.startTime', self._make_timestamp(T0)), ('aggregation.perSeriesAligner', ALIGNER), ('aggregation.alignmentPeriod', PERIOD), ('aggregation.crossSeriesReducer', REDUCER), @@ -318,8 +323,8 @@ def test_iteration(self): 'path': '/projects/{project}/timeSeries/'.format(project=PROJECT), 'query_params': [ ('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)), - ('interval.endTime', T1.isoformat() + 'Z'), - ('interval.startTime', T0.isoformat() + 'Z'), + ('interval.endTime', self._make_timestamp(T1)), + ('interval.startTime', self._make_timestamp(T0)), ], } @@ -398,8 +403,8 @@ def test_iteration_paged(self): 'path': '/projects/{project}/timeSeries/'.format(project=PROJECT), 'query_params': [ ('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)), - ('interval.endTime', T1.isoformat() + 'Z'), - ('interval.startTime', T0.isoformat() + 'Z'), + ('interval.endTime', self._make_timestamp(T1)), + ('interval.startTime', self._make_timestamp(T0)), ], } @@ -432,8 +437,8 @@ def test_iteration_empty(self): 'path': '/projects/{project}/timeSeries/'.format(project=PROJECT), 'query_params': [ ('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)), - ('interval.endTime', T1.isoformat() + 'Z'), - ('interval.startTime', T0.isoformat() + 'Z'), + ('interval.endTime', self._make_timestamp(T1)), + ('interval.startTime', self._make_timestamp(T0)), ], } request, = connection._requested @@ -482,8 +487,8 @@ def test_iteration_headers_only(self): 'path': '/projects/{project}/timeSeries/'.format(project=PROJECT), 'query_params': [ ('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)), - ('interval.endTime', T1.isoformat() + 'Z'), - ('interval.startTime', T0.isoformat() + 'Z'), + ('interval.endTime', self._make_timestamp(T1)), + ('interval.startTime', self._make_timestamp(T0)), ('view', 'HEADERS'), ], } @@ -596,26 +601,6 @@ def test_resource_type_suffix(self): self.assertEqual(actual, expected) -class Test__format_timestamp(unittest2.TestCase): - - def _callFUT(self, timestamp): - from gcloud.monitoring.query import _format_timestamp - return _format_timestamp(timestamp) - - def test_naive(self): - from datetime import datetime - TIMESTAMP = datetime(2016, 4, 5, 13, 30, 0) - timestamp = self._callFUT(TIMESTAMP) - self.assertEqual(timestamp, '2016-04-05T13:30:00Z') - - def test_with_timezone(self): - from datetime import datetime - from gcloud._helpers import UTC - TIMESTAMP = datetime(2016, 4, 5, 13, 30, 0, tzinfo=UTC) - timestamp = self._callFUT(TIMESTAMP) - self.assertEqual(timestamp, '2016-04-05T13:30:00Z') - - class _Connection(object): def __init__(self, *responses): diff --git a/gcloud/test__helpers.py b/gcloud/test__helpers.py index 4af1082cc551..35636fdd722a 100644 --- a/gcloud/test__helpers.py +++ b/gcloud/test__helpers.py @@ -648,28 +648,52 @@ def test_w_naonseconds(self): class Test__datetime_to_rfc3339(unittest2.TestCase): - def _callFUT(self, value): + def _callFUT(self, *args, **kwargs): from gcloud._helpers import _datetime_to_rfc3339 - return _datetime_to_rfc3339(value) + return _datetime_to_rfc3339(*args, **kwargs) - def test_it(self): + @staticmethod + def _make_timezone(offset): + from gcloud._helpers import _UTC + + class CET(_UTC): + _tzname = 'CET' + _utcoffset = offset + + return CET() + + def test_w_utc_datetime(self): import datetime from gcloud._helpers import UTC - year = 2009 - month = 12 - day = 17 - hour = 12 - minute = 44 - seconds = 32 - micros = 123456 + TIMESTAMP = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=UTC) + result = self._callFUT(TIMESTAMP, ignore_zone=False) + self.assertEqual(result, '2016-04-05T13:30:00.000000Z') - 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) + def test_w_non_utc_datetime(self): + import datetime + from gcloud._helpers import _UTC + + zone = self._make_timezone(offset=datetime.timedelta(hours=-1)) + TIMESTAMP = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone) + result = self._callFUT(TIMESTAMP, ignore_zone=False) + self.assertEqual(result, '2016-04-05T14:30:00.000000Z') + + def test_w_non_utc_datetime_and_ignore_zone(self): + import datetime + from gcloud._helpers import _UTC + + zone = self._make_timezone(offset=datetime.timedelta(hours=-1)) + TIMESTAMP = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone) + result = self._callFUT(TIMESTAMP) + self.assertEqual(result, '2016-04-05T13:30:00.000000Z') + + def test_w_naive_datetime(self): + import datetime + + TIMESTAMP = datetime.datetime(2016, 4, 5, 13, 30, 0) + result = self._callFUT(TIMESTAMP) + self.assertEqual(result, '2016-04-05T13:30:00.000000Z') class Test__to_bytes(unittest2.TestCase): From 1f55e4df9ef1cf4422951ea8a5973f06da7d60e4 Mon Sep 17 00:00:00 2001 From: Supriya Garg Date: Mon, 1 Aug 2016 18:30:44 -0400 Subject: [PATCH 2/2] Minor edit for making code more readable. --- gcloud/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcloud/_helpers.py b/gcloud/_helpers.py index 58729bab73c9..3b4f7aabc32f 100644 --- a/gcloud/_helpers.py +++ b/gcloud/_helpers.py @@ -422,7 +422,7 @@ def _datetime_to_rfc3339(value, ignore_zone=True): :rtype: str :returns: The string representing the datetime stamp. """ - if not(ignore_zone or value.tzinfo is None): + if not ignore_zone and value.tzinfo is not None: # Convert to UTC and remove the time zone info. value = value.replace(tzinfo=None) - value.utcoffset()