diff --git a/bigquery/google/cloud/bigquery/_helpers.py b/bigquery/google/cloud/bigquery/_helpers.py index dcea7b237a07..b72b55950b71 100644 --- a/bigquery/google/cloud/bigquery/_helpers.py +++ b/bigquery/google/cloud/bigquery/_helpers.py @@ -18,10 +18,10 @@ from collections import OrderedDict import datetime +from google.cloud._helpers import UTC from google.cloud._helpers import _date_from_iso8601_date from google.cloud._helpers import _datetime_from_microseconds from google.cloud._helpers import _datetime_to_rfc3339 -from google.cloud._helpers import _microseconds_from_datetime from google.cloud._helpers import _RFC3339_NO_FRACTION from google.cloud._helpers import _time_from_iso8601_time_naive from google.cloud._helpers import _to_bytes @@ -150,7 +150,11 @@ def _bytes_to_json(value): def _timestamp_to_json(value): """Coerce 'value' to an JSON-compatible representation.""" if isinstance(value, datetime.datetime): - value = _microseconds_from_datetime(value) / 1.0e6 + if value.tzinfo not in (None, UTC): + # Convert to UTC and remove the time zone info. + value = value.replace(tzinfo=None) - value.utcoffset() + value = '%s %s+00:00' % ( + value.date().isoformat(), value.time().isoformat()) return value diff --git a/bigquery/unit_tests/test__helpers.py b/bigquery/unit_tests/test__helpers.py index 0e508aba2da0..affd52294fdd 100644 --- a/bigquery/unit_tests/test__helpers.py +++ b/bigquery/unit_tests/test__helpers.py @@ -546,13 +546,35 @@ def _call_fut(self, value): def test_w_float(self): self.assertEqual(self._call_fut(1.234567), 1.234567) - def test_w_datetime(self): + def test_w_string(self): + ZULU = '2016-12-20 15:58:27.339328+00:00' + self.assertEqual(self._call_fut(ZULU), ZULU) + + def test_w_datetime_wo_zone(self): + import datetime + ZULU = '2016-12-20 15:58:27.339328+00:00' + when = datetime.datetime(2016, 12, 20, 15, 58, 27, 339328) + self.assertEqual(self._call_fut(when), ZULU) + + def test_w_datetime_w_non_utc_zone(self): + import datetime + + class _Zone(datetime.tzinfo): + + def utcoffset(self, _): + return datetime.timedelta(minutes=-240) + + ZULU = '2016-12-20 19:58:27.339328+00:00' + when = datetime.datetime( + 2016, 12, 20, 15, 58, 27, 339328, tzinfo=_Zone()) + self.assertEqual(self._call_fut(when), ZULU) + + def test_w_datetime_w_utc_zone(self): import datetime from google.cloud._helpers import UTC - from google.cloud._helpers import _microseconds_from_datetime - when = datetime.datetime(2016, 12, 3, 14, 11, 27, tzinfo=UTC) - self.assertEqual(self._call_fut(when), - _microseconds_from_datetime(when) / 1e6) + ZULU = '2016-12-20 15:58:27.339328+00:00' + when = datetime.datetime(2016, 12, 20, 15, 58, 27, 339328, tzinfo=UTC) + self.assertEqual(self._call_fut(when), ZULU) class Test_datetime_to_json(unittest.TestCase): @@ -907,20 +929,20 @@ def test_to_api_repr_w_bool(self): self.assertEqual(param.to_api_repr(), EXPECTED) def test_to_api_repr_w_timestamp_datetime(self): + from google.cloud._helpers import UTC import datetime - from google.cloud._helpers import _microseconds_from_datetime - now = datetime.datetime.utcnow() - seconds = _microseconds_from_datetime(now) / 1.0e6 + STAMP = '2016-12-20 15:58:27.339328+00:00' + when = datetime.datetime(2016, 12, 20, 15, 58, 27, 339328, tzinfo=UTC) EXPECTED = { 'parameterType': { 'type': 'TIMESTAMP', }, 'parameterValue': { - 'value': seconds, + 'value': STAMP, }, } klass = self._get_target_class() - param = klass.positional(type_='TIMESTAMP', value=now) + param = klass.positional(type_='TIMESTAMP', value=when) self.assertEqual(param.to_api_repr(), EXPECTED) def test_to_api_repr_w_timestamp_micros(self): diff --git a/system_tests/bigquery.py b/system_tests/bigquery.py index 3c266c760440..5b58ad2ee4da 100644 --- a/system_tests/bigquery.py +++ b/system_tests/bigquery.py @@ -482,9 +482,12 @@ def _job_done(instance): def test_sync_query_w_standard_sql_types(self): import datetime from google.cloud._helpers import UTC + from google.cloud.bigquery._helpers import ScalarQueryParameter naive = datetime.datetime(2016, 12, 5, 12, 41, 9) stamp = "%s %s" % (naive.date().isoformat(), naive.time().isoformat()) zoned = naive.replace(tzinfo=UTC) + zoned_param = ScalarQueryParameter( + name='zoned', type_='TIMESTAMP', value=zoned) EXAMPLES = [ { 'sql': 'SELECT 1', @@ -553,9 +556,16 @@ def test_sync_query_w_standard_sql_types(self): 'sql': 'SELECT ARRAY(SELECT STRUCT([1, 2]))', 'expected': [{u'_field_1': [1, 2]}], }, + { + 'sql': 'SELECT @zoned', + 'expected': zoned, + 'query_parameters': [zoned_param], + }, ] for example in EXAMPLES: - query = Config.CLIENT.run_sync_query(example['sql']) + query = Config.CLIENT.run_sync_query( + example['sql'], + query_parameters=example.get('query_parameters', ())) query.use_legacy_sql = False query.run() self.assertEqual(len(query.rows), 1)