Repository: googleapis/google-cloud-python

Issue:
Support both seconds from epoch and timestamp string
If someone already has a timestamp string, we shouldn't force them to convert it to `datetime.datetime` so that we can in turn [convert that](https://github.com/GoogleCloudPlatform/gcloud-python/blob/0c74a599b6aa0a16e21575998e1770182c05e12c/gcloud/bigquery/table.py#L665-L669) into seconds. (Inspired by #1381.)

From [the docs](https://cloud.google.com/bigquery/preparing-data-for-bigquery#datatype)

> TIMESTAMP data types can be described in two ways: UNIX timestamps or calendar datetimes. BigQuery stores TIMESTAMP data internally as a UNIX timestamp with microsecond precision.

so we could allow the user to just pass a string and we "take their word for it" that it's a valid datetime. Since `Table.insert_data` returns a list of errors anyhow, we wouldn't have to do much/anything to enforce this.



Discussion:

Resolving patch:
diff --git a/bigquery/google/cloud/bigquery/table.py b/bigquery/google/cloud/bigquery/table.py
--- a/bigquery/google/cloud/bigquery/table.py
+++ b/bigquery/google/cloud/bigquery/table.py
@@ -742,11 +742,11 @@ def insert_data(self,
             row_info = {}
 
             for field, value in zip(self._schema, row):
-                if field.field_type == 'TIMESTAMP' and value is not None:
+                if field.field_type == 'TIMESTAMP':
                     # BigQuery stores TIMESTAMP data internally as a
                     # UNIX timestamp with microsecond precision.
                     # Specifies the number of seconds since the epoch.
-                    value = _microseconds_from_datetime(value) * 1e-6
+                    value = _convert_timestamp(value)
                 row_info[field.name] = value
 
             info = {'json': row_info}
@@ -1129,3 +1129,10 @@ class _UrlBuilder(object):
     def __init__(self):
         self.query_params = {}
         self._relative_path = ''
+
+
+def _convert_timestamp(value):
+    """Helper for :meth:`Table.insert_data`."""
+    if isinstance(value, datetime.datetime):
+        value = _microseconds_from_datetime(value) * 1e-6
+    return value


Test patch:
diff --git a/bigquery/unit_tests/test_table.py b/bigquery/unit_tests/test_table.py
--- a/bigquery/unit_tests/test_table.py
+++ b/bigquery/unit_tests/test_table.py
@@ -1296,6 +1296,7 @@ def test_insert_data_wo_schema(self):
     def test_insert_data_w_bound_client(self):
         import datetime
         from google.cloud._helpers import UTC
+        from google.cloud._helpers import _datetime_to_rfc3339
         from google.cloud._helpers import _microseconds_from_datetime
         from google.cloud.bigquery.table import SchemaField
 
@@ -1313,16 +1314,16 @@ def test_insert_data_w_bound_client(self):
         table = self._make_one(self.TABLE_NAME, dataset=dataset,
                                schema=[full_name, age, joined])
         ROWS = [
-            ('Phred Phlyntstone', 32, WHEN),
+            ('Phred Phlyntstone', 32, _datetime_to_rfc3339(WHEN)),
             ('Bharney Rhubble', 33, WHEN + datetime.timedelta(seconds=1)),
             ('Wylma Phlyntstone', 29, WHEN + datetime.timedelta(seconds=2)),
             ('Bhettye Rhubble', 27, None),
         ]
 
         def _row_data(row):
-            joined = None
-            if row[2] is not None:
-                joined = _microseconds_from_datetime(row[2]) * 1e-6
+            joined = row[2]
+            if isinstance(row[2], datetime.datetime):
+                joined = _microseconds_from_datetime(joined) * 1e-6
             return {'full_name': row[0],
                     'age': row[1],
                     'joined': joined}