Skip to content

Commit 5a6ba14

Browse files
authored
Merge pull request #2041 from supriyagarg/rfc3339
Update the helper _datetime_to_rfc3339 to handle time zones.
2 parents 3478b9c + 1f55e4d commit 5a6ba14

File tree

5 files changed

+75
-70
lines changed

5 files changed

+75
-70
lines changed

gcloud/_helpers.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,15 +409,23 @@ def _rfc3339_nanos_to_datetime(dt_str):
409409
return bare_seconds.replace(microsecond=micros, tzinfo=UTC)
410410

411411

412-
def _datetime_to_rfc3339(value):
413-
"""Convert a native timestamp to a string.
412+
def _datetime_to_rfc3339(value, ignore_zone=True):
413+
"""Convert a timestamp to a string.
414414
415415
:type value: :class:`datetime.datetime`
416416
:param value: The datetime object to be converted to a string.
417417
418+
:type ignore_zone: boolean
419+
:param ignore_zone: If True, then the timezone (if any) of the datetime
420+
object is ignored.
421+
418422
:rtype: str
419423
:returns: The string representing the datetime stamp.
420424
"""
425+
if not ignore_zone and value.tzinfo is not None:
426+
# Convert to UTC and remove the time zone info.
427+
value = value.replace(tzinfo=None) - value.utcoffset()
428+
421429
return value.strftime(_RFC3339_MICROS)
422430

423431

gcloud/monitoring/query.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import six
2727

28+
from gcloud._helpers import _datetime_to_rfc3339
2829
from gcloud.monitoring._dataframe import _build_dataframe
2930
from gcloud.monitoring.timeseries import TimeSeries
3031

@@ -498,9 +499,12 @@ def _build_query_params(self, headers_only=False,
498499
"""
499500
yield 'filter', self.filter
500501

501-
yield 'interval.endTime', _format_timestamp(self._end_time)
502+
yield 'interval.endTime', _datetime_to_rfc3339(
503+
self._end_time, ignore_zone=False)
504+
502505
if self._start_time is not None:
503-
yield 'interval.startTime', _format_timestamp(self._start_time)
506+
yield 'interval.startTime', _datetime_to_rfc3339(
507+
self._start_time, ignore_zone=False)
504508

505509
if self._per_series_aligner is not None:
506510
yield 'aggregation.perSeriesAligner', self._per_series_aligner
@@ -651,20 +655,3 @@ def _build_label_filter(category, *args, **kwargs):
651655
terms.append(term.format(key=key, value=value))
652656

653657
return ' AND '.join(sorted(terms))
654-
655-
656-
def _format_timestamp(timestamp):
657-
"""Convert a datetime object to a string as required by the API.
658-
659-
:type timestamp: :class:`datetime.datetime`
660-
:param timestamp: A datetime object.
661-
662-
:rtype: string
663-
:returns: The formatted timestamp. For example:
664-
``"2016-02-17T19:18:01.763000Z"``
665-
"""
666-
if timestamp.tzinfo is not None:
667-
# Convert to UTC and remove the time zone info.
668-
timestamp = timestamp.replace(tzinfo=None) - timestamp.utcoffset()
669-
670-
return timestamp.isoformat() + 'Z'

gcloud/monitoring/test_client.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def _makeOne(self, *args, **kwargs):
2828

2929
def test_query(self):
3030
import datetime
31+
from gcloud._helpers import _datetime_to_rfc3339
3132
from gcloud.exceptions import NotFound
3233

3334
START_TIME = datetime.datetime(2016, 4, 6, 22, 5, 0)
@@ -119,8 +120,8 @@ def P(timestamp, value):
119120
'path': '/projects/{project}/timeSeries/'.format(project=PROJECT),
120121
'query_params': [
121122
('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)),
122-
('interval.endTime', END_TIME.isoformat() + 'Z'),
123-
('interval.startTime', START_TIME.isoformat() + 'Z'),
123+
('interval.endTime', _datetime_to_rfc3339(END_TIME)),
124+
('interval.startTime', _datetime_to_rfc3339(START_TIME)),
124125
],
125126
}
126127

gcloud/monitoring/test_query.py

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ def _getTargetClass(self):
8080
def _makeOne(self, *args, **kwargs):
8181
return self._getTargetClass()(*args, **kwargs)
8282

83+
@staticmethod
84+
def _make_timestamp(value):
85+
from gcloud._helpers import _datetime_to_rfc3339
86+
return _datetime_to_rfc3339(value)
87+
8388
def test_constructor_minimal(self):
8489
client = _Client(project=PROJECT, connection=_Connection())
8590
query = self._makeOne(client)
@@ -217,7 +222,7 @@ def test_request_parameters_minimal(self):
217222
actual = list(query._build_query_params())
218223
expected = [
219224
('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)),
220-
('interval.endTime', T1.isoformat() + 'Z'),
225+
('interval.endTime', self._make_timestamp(T1)),
221226
]
222227
self.assertEqual(actual, expected)
223228

@@ -246,8 +251,8 @@ def test_request_parameters_maximal(self):
246251
page_token=PAGE_TOKEN))
247252
expected = [
248253
('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)),
249-
('interval.endTime', T1.isoformat() + 'Z'),
250-
('interval.startTime', T0.isoformat() + 'Z'),
254+
('interval.endTime', self._make_timestamp(T1)),
255+
('interval.startTime', self._make_timestamp(T0)),
251256
('aggregation.perSeriesAligner', ALIGNER),
252257
('aggregation.alignmentPeriod', PERIOD),
253258
('aggregation.crossSeriesReducer', REDUCER),
@@ -318,8 +323,8 @@ def test_iteration(self):
318323
'path': '/projects/{project}/timeSeries/'.format(project=PROJECT),
319324
'query_params': [
320325
('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)),
321-
('interval.endTime', T1.isoformat() + 'Z'),
322-
('interval.startTime', T0.isoformat() + 'Z'),
326+
('interval.endTime', self._make_timestamp(T1)),
327+
('interval.startTime', self._make_timestamp(T0)),
323328
],
324329
}
325330

@@ -398,8 +403,8 @@ def test_iteration_paged(self):
398403
'path': '/projects/{project}/timeSeries/'.format(project=PROJECT),
399404
'query_params': [
400405
('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)),
401-
('interval.endTime', T1.isoformat() + 'Z'),
402-
('interval.startTime', T0.isoformat() + 'Z'),
406+
('interval.endTime', self._make_timestamp(T1)),
407+
('interval.startTime', self._make_timestamp(T0)),
403408
],
404409
}
405410

@@ -432,8 +437,8 @@ def test_iteration_empty(self):
432437
'path': '/projects/{project}/timeSeries/'.format(project=PROJECT),
433438
'query_params': [
434439
('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)),
435-
('interval.endTime', T1.isoformat() + 'Z'),
436-
('interval.startTime', T0.isoformat() + 'Z'),
440+
('interval.endTime', self._make_timestamp(T1)),
441+
('interval.startTime', self._make_timestamp(T0)),
437442
],
438443
}
439444
request, = connection._requested
@@ -482,8 +487,8 @@ def test_iteration_headers_only(self):
482487
'path': '/projects/{project}/timeSeries/'.format(project=PROJECT),
483488
'query_params': [
484489
('filter', 'metric.type = "{type}"'.format(type=METRIC_TYPE)),
485-
('interval.endTime', T1.isoformat() + 'Z'),
486-
('interval.startTime', T0.isoformat() + 'Z'),
490+
('interval.endTime', self._make_timestamp(T1)),
491+
('interval.startTime', self._make_timestamp(T0)),
487492
('view', 'HEADERS'),
488493
],
489494
}
@@ -596,26 +601,6 @@ def test_resource_type_suffix(self):
596601
self.assertEqual(actual, expected)
597602

598603

599-
class Test__format_timestamp(unittest2.TestCase):
600-
601-
def _callFUT(self, timestamp):
602-
from gcloud.monitoring.query import _format_timestamp
603-
return _format_timestamp(timestamp)
604-
605-
def test_naive(self):
606-
from datetime import datetime
607-
TIMESTAMP = datetime(2016, 4, 5, 13, 30, 0)
608-
timestamp = self._callFUT(TIMESTAMP)
609-
self.assertEqual(timestamp, '2016-04-05T13:30:00Z')
610-
611-
def test_with_timezone(self):
612-
from datetime import datetime
613-
from gcloud._helpers import UTC
614-
TIMESTAMP = datetime(2016, 4, 5, 13, 30, 0, tzinfo=UTC)
615-
timestamp = self._callFUT(TIMESTAMP)
616-
self.assertEqual(timestamp, '2016-04-05T13:30:00Z')
617-
618-
619604
class _Connection(object):
620605

621606
def __init__(self, *responses):

gcloud/test__helpers.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -648,28 +648,52 @@ def test_w_naonseconds(self):
648648

649649
class Test__datetime_to_rfc3339(unittest2.TestCase):
650650

651-
def _callFUT(self, value):
651+
def _callFUT(self, *args, **kwargs):
652652
from gcloud._helpers import _datetime_to_rfc3339
653-
return _datetime_to_rfc3339(value)
653+
return _datetime_to_rfc3339(*args, **kwargs)
654654

655-
def test_it(self):
655+
@staticmethod
656+
def _make_timezone(offset):
657+
from gcloud._helpers import _UTC
658+
659+
class CET(_UTC):
660+
_tzname = 'CET'
661+
_utcoffset = offset
662+
663+
return CET()
664+
665+
def test_w_utc_datetime(self):
656666
import datetime
657667
from gcloud._helpers import UTC
658668

659-
year = 2009
660-
month = 12
661-
day = 17
662-
hour = 12
663-
minute = 44
664-
seconds = 32
665-
micros = 123456
669+
TIMESTAMP = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=UTC)
670+
result = self._callFUT(TIMESTAMP, ignore_zone=False)
671+
self.assertEqual(result, '2016-04-05T13:30:00.000000Z')
666672

667-
to_convert = datetime.datetime(
668-
year, month, day, hour, minute, seconds, micros, UTC)
669-
dt_str = '%d-%02d-%02dT%02d:%02d:%02d.%06dZ' % (
670-
year, month, day, hour, minute, seconds, micros)
671-
result = self._callFUT(to_convert)
672-
self.assertEqual(result, dt_str)
673+
def test_w_non_utc_datetime(self):
674+
import datetime
675+
from gcloud._helpers import _UTC
676+
677+
zone = self._make_timezone(offset=datetime.timedelta(hours=-1))
678+
TIMESTAMP = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone)
679+
result = self._callFUT(TIMESTAMP, ignore_zone=False)
680+
self.assertEqual(result, '2016-04-05T14:30:00.000000Z')
681+
682+
def test_w_non_utc_datetime_and_ignore_zone(self):
683+
import datetime
684+
from gcloud._helpers import _UTC
685+
686+
zone = self._make_timezone(offset=datetime.timedelta(hours=-1))
687+
TIMESTAMP = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone)
688+
result = self._callFUT(TIMESTAMP)
689+
self.assertEqual(result, '2016-04-05T13:30:00.000000Z')
690+
691+
def test_w_naive_datetime(self):
692+
import datetime
693+
694+
TIMESTAMP = datetime.datetime(2016, 4, 5, 13, 30, 0)
695+
result = self._callFUT(TIMESTAMP)
696+
self.assertEqual(result, '2016-04-05T13:30:00.000000Z')
673697

674698

675699
class Test__to_bytes(unittest2.TestCase):

0 commit comments

Comments
 (0)