cloud.google.com/go/bigquery: respect microsecond precision of Timestamps

With this change, the time.Time values returned by convertBasicType
always correspond to a round number of microseconds since the epoch,
which matches the microsecond precision that BigQuery maintains
internally.

Change-Id: I0adb0aaaa918c184eb8ba4a888396f41f1a94d14
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/44530
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Seth Hollyman <shollyman@google.com>
diff --git a/bigquery/value.go b/bigquery/value.go
index 128dd41..a7c3ed0 100644
--- a/bigquery/value.go
+++ b/bigquery/value.go
@@ -870,8 +870,10 @@
 			return nil, err
 		}
 		secs := math.Trunc(f)
-		nanos := (f - secs) * 1e9
-		return Value(time.Unix(int64(secs), int64(nanos)).UTC()), nil
+		// Timestamps in BigQuery have microsecond precision, so we must
+		// return a round number of microseconds.
+		micros := math.Trunc((f-secs)*1e6 + 0.5)
+		return Value(time.Unix(int64(secs), int64(micros)*1000).UTC()), nil
 	case DateFieldType:
 		return civil.ParseDate(val)
 	case TimeFieldType:
diff --git a/bigquery/value_test.go b/bigquery/value_test.go
index 7540c05..1e5bb2d 100644
--- a/bigquery/value_test.go
+++ b/bigquery/value_test.go
@@ -106,6 +106,44 @@
 	}
 }
 
+func TestConvertTimePrecision(t *testing.T) {
+	tcs := []struct {
+		// Internally, BigQuery stores timestamps as microsecond-precision
+		// floats.
+		bq   float64
+		want time.Time
+	}{
+		{
+			bq:   1555593697.154358,
+			want: time.Unix(1555593697, 154358*1000),
+		},
+		{
+			bq:   1555593697.154359,
+			want: time.Unix(1555593697, 154359*1000),
+		},
+		{
+			bq:   1555593697.154360,
+			want: time.Unix(1555593697, 154360*1000),
+		},
+	}
+	for _, tc := range tcs {
+		bqS := fmt.Sprintf("%.6f", tc.bq)
+		t.Run(bqS, func(t *testing.T) {
+			got, err := convertBasicType(bqS, TimestampFieldType)
+			if err != nil {
+				t.Fatalf("convertBasicType failed: %v", err)
+			}
+			gotT, ok := got.(time.Time)
+			if !ok {
+				t.Fatalf("got a %T from convertBasicType, want a time.Time; got = %v", got, got)
+			}
+			if !gotT.Equal(tc.want) {
+				t.Errorf("got %v from convertBasicType, want %v", gotT, tc.want)
+			}
+		})
+	}
+}
+
 func TestConvertNullValues(t *testing.T) {
 	schema := Schema{{Type: StringFieldType}}
 	row := &bq.TableRow{