Skip to content

Commit fc4442e

Browse files
committed
Send TIMESTAMP query parameters as string.
- *Not* the float-time-since-epoch-in-seconds which Bigquery uses for all other TIMESTAMP values. :( - *Not* RFC3339, but the SQL-mandated format with an embedded space. :( Closes: #2886.
1 parent 3d354dc commit fc4442e

File tree

3 files changed

+49
-13
lines changed

3 files changed

+49
-13
lines changed

bigquery/google/cloud/bigquery/_helpers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
from collections import OrderedDict
1919
import datetime
2020

21+
from google.cloud._helpers import UTC
2122
from google.cloud._helpers import _date_from_iso8601_date
2223
from google.cloud._helpers import _datetime_from_microseconds
2324
from google.cloud._helpers import _datetime_to_rfc3339
24-
from google.cloud._helpers import _microseconds_from_datetime
2525
from google.cloud._helpers import _RFC3339_NO_FRACTION
2626
from google.cloud._helpers import _time_from_iso8601_time_naive
2727
from google.cloud._helpers import _to_bytes
@@ -150,7 +150,11 @@ def _bytes_to_json(value):
150150
def _timestamp_to_json(value):
151151
"""Coerce 'value' to an JSON-compatible representation."""
152152
if isinstance(value, datetime.datetime):
153-
value = _microseconds_from_datetime(value) / 1.0e6
153+
if value.tzinfo not in (None, UTC):
154+
# Convert to UTC and remove the time zone info.
155+
value = value.replace(tzinfo=None) - value.utcoffset()
156+
value = '%s %s+00:00' % (
157+
value.date().isoformat(), value.time().isoformat())
154158
return value
155159

156160

bigquery/unit_tests/test__helpers.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -546,13 +546,35 @@ def _call_fut(self, value):
546546
def test_w_float(self):
547547
self.assertEqual(self._call_fut(1.234567), 1.234567)
548548

549-
def test_w_datetime(self):
549+
def test_w_string(self):
550+
ZULU = '2016-12-20 15:58:27.339328+00:00'
551+
self.assertEqual(self._call_fut(ZULU), ZULU)
552+
553+
def test_w_datetime_wo_zone(self):
554+
import datetime
555+
ZULU = '2016-12-20 15:58:27.339328+00:00'
556+
when = datetime.datetime(2016, 12, 20, 15, 58, 27, 339328)
557+
self.assertEqual(self._call_fut(when), ZULU)
558+
559+
def test_w_datetime_w_non_utc_zone(self):
560+
import datetime
561+
562+
class _Zone(datetime.tzinfo):
563+
564+
def utcoffset(self, _):
565+
return datetime.timedelta(minutes=-240)
566+
567+
ZULU = '2016-12-20 19:58:27.339328+00:00'
568+
when = datetime.datetime(
569+
2016, 12, 20, 15, 58, 27, 339328, tzinfo=_Zone())
570+
self.assertEqual(self._call_fut(when), ZULU)
571+
572+
def test_w_datetime_w_utc_zone(self):
550573
import datetime
551574
from google.cloud._helpers import UTC
552-
from google.cloud._helpers import _microseconds_from_datetime
553-
when = datetime.datetime(2016, 12, 3, 14, 11, 27, tzinfo=UTC)
554-
self.assertEqual(self._call_fut(when),
555-
_microseconds_from_datetime(when) / 1e6)
575+
ZULU = '2016-12-20 15:58:27.339328+00:00'
576+
when = datetime.datetime(2016, 12, 20, 15, 58, 27, 339328, tzinfo=UTC)
577+
self.assertEqual(self._call_fut(when), ZULU)
556578

557579

558580
class Test_datetime_to_json(unittest.TestCase):
@@ -907,20 +929,20 @@ def test_to_api_repr_w_bool(self):
907929
self.assertEqual(param.to_api_repr(), EXPECTED)
908930

909931
def test_to_api_repr_w_timestamp_datetime(self):
932+
from google.cloud._helpers import UTC
910933
import datetime
911-
from google.cloud._helpers import _microseconds_from_datetime
912-
now = datetime.datetime.utcnow()
913-
seconds = _microseconds_from_datetime(now) / 1.0e6
934+
STAMP = '2016-12-20 15:58:27.339328+00:00'
935+
when = datetime.datetime(2016, 12, 20, 15, 58, 27, 339328, tzinfo=UTC)
914936
EXPECTED = {
915937
'parameterType': {
916938
'type': 'TIMESTAMP',
917939
},
918940
'parameterValue': {
919-
'value': seconds,
941+
'value': STAMP,
920942
},
921943
}
922944
klass = self._get_target_class()
923-
param = klass.positional(type_='TIMESTAMP', value=now)
945+
param = klass.positional(type_='TIMESTAMP', value=when)
924946
self.assertEqual(param.to_api_repr(), EXPECTED)
925947

926948
def test_to_api_repr_w_timestamp_micros(self):

system_tests/bigquery.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,9 +482,12 @@ def _job_done(instance):
482482
def test_sync_query_w_standard_sql_types(self):
483483
import datetime
484484
from google.cloud._helpers import UTC
485+
from google.cloud.bigquery._helpers import ScalarQueryParameter
485486
naive = datetime.datetime(2016, 12, 5, 12, 41, 9)
486487
stamp = "%s %s" % (naive.date().isoformat(), naive.time().isoformat())
487488
zoned = naive.replace(tzinfo=UTC)
489+
zoned_param = ScalarQueryParameter(
490+
name='zoned', type_='TIMESTAMP', value=zoned)
488491
EXAMPLES = [
489492
{
490493
'sql': 'SELECT 1',
@@ -553,9 +556,16 @@ def test_sync_query_w_standard_sql_types(self):
553556
'sql': 'SELECT ARRAY(SELECT STRUCT([1, 2]))',
554557
'expected': [{u'_field_1': [1, 2]}],
555558
},
559+
{
560+
'sql': 'SELECT @zoned',
561+
'expected': zoned,
562+
'query_parameters': [zoned_param],
563+
},
556564
]
557565
for example in EXAMPLES:
558-
query = Config.CLIENT.run_sync_query(example['sql'])
566+
query = Config.CLIENT.run_sync_query(
567+
example['sql'],
568+
query_parameters=example.get('query_parameters', ()))
559569
query.use_legacy_sql = False
560570
query.run()
561571
self.assertEqual(len(query.rows), 1)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy