From db6bfd99a0165e0e1aedefb26e5b570d9aebaa27 Mon Sep 17 00:00:00 2001 From: Dave Supplee Date: Thu, 2 Mar 2017 20:23:01 -0500 Subject: [PATCH] ensure timestamps retain microsecond precision --- src/BigQuery/ValueMapper.php | 49 +++++++++++++++++++++++-- tests/unit/BigQuery/ValueMapperTest.php | 17 ++++++++- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/BigQuery/ValueMapper.php b/src/BigQuery/ValueMapper.php index 2d6bfe0b481..9121dcc2b58 100644 --- a/src/BigQuery/ValueMapper.php +++ b/src/BigQuery/ValueMapper.php @@ -106,10 +106,7 @@ public function fromBigQuery(array $value, array $schema) case self::TYPE_TIME: return new Time(new \DateTime($value)); case self::TYPE_TIMESTAMP: - $timestamp = new \DateTime(); - $timestamp->setTimestamp((float) $value); - - return new Timestamp($timestamp); + return $this->timestampFromBigQuery($value); case self::TYPE_RECORD: return $this->recordFromBigQuery($value, $schema['fields']); default: @@ -325,4 +322,48 @@ private function assocArrayToParameter(array $struct) ['structValues' => $values] ]; } + + /** + * Converts a timestamp in string format received from BigQuery to a + * {@see Google\Cloud\BigQuery\Timestamp}. + * + * @param string $value The timestamp. + * @return Timestamp + */ + private function timestampFromBigQuery($value) + { + // If the string contains 'E' convert from exponential notation to + // decimal notation. This doesn't cast to a float because precision can + // be lost. + if (strpos($value, 'E')) { + list($value, $exponent) = explode('E', $value); + list($firstDigit, $remainingDigits) = explode('.', $value); + + if (strlen($remainingDigits) > $exponent) { + $value = $firstDigit . substr_replace($remainingDigits, '.', $exponent, 0); + } else { + $value = $firstDigit . str_pad($remainingDigits, $exponent, '0') . '.0'; + } + } + + list($unixTimestamp, $microSeconds) = explode('.', $value); + $dateTime = new \DateTime("@$unixTimestamp"); + + // If the timestamp is before the epoch, make sure we account for that + // before concatenating the microseconds. + if ($microSeconds > 0 && $unixTimestamp[0] === '-') { + $microSeconds = 1000000 - (int) str_pad($microSeconds, 6, '0'); + $dateTime->modify('-1 second'); + } + + return new Timestamp( + new \DateTime( + sprintf( + '%s.%s+00:00', + $dateTime->format('Y-m-d H:i:s'), + $microSeconds + ) + ) + ); + } } diff --git a/tests/unit/BigQuery/ValueMapperTest.php b/tests/unit/BigQuery/ValueMapperTest.php index 58a57ccc255..157ba271958 100644 --- a/tests/unit/BigQuery/ValueMapperTest.php +++ b/tests/unit/BigQuery/ValueMapperTest.php @@ -134,10 +134,25 @@ public function bigQueryValueProvider() new Time(new \DateTime('12:15:15')) ], [ - ['v' => '1438712914'], + ['v' => '1.438712914E9'], ['type' => 'TIMESTAMP'], new Timestamp(new \DateTime('2015-08-04 18:28:34Z')) ], + [ + ['v' => '2678400.0'], + ['type' => 'TIMESTAMP'], + new Timestamp(new \DateTime('1970-02-01')) + ], + [ + ['v' => '-3.1561919984985E8'], + ['type' => 'TIMESTAMP'], + new Timestamp(new \DateTime('1960-01-01 00:00:00.150150Z')) + ], + [ + ['v' => '9.4668480015015E8'], + ['type' => 'TIMESTAMP'], + new Timestamp(new \DateTime('2000-01-01 00:00:00.150150Z')) + ], [ [ 'v' => [