Skip to content

Commit b5801a6

Browse files
authored
feat: add support for microseconds precision (#1192)
Parses timestamps from the backend ( that arrives as a float64 ) using the [@google-cloud/precise-date](https://www.npmjs.com/package/@google-cloud/precise-date) lib to support microsecond resolution. Example output: ``` const bigquery = new BigQuery(); const [rows] = await bigquery.query({ query: 'SELECT TIMESTAMP("2014-09-27 12:30:00.123456Z")', }); console.log(JSON.stringify(rows)); // [{"f0_":{"value":"2014-09-27T12:30:00.123456000Z"}}] ``` Fixes #6
1 parent c8fba1a commit b5801a6

File tree

3 files changed

+62
-7
lines changed

3 files changed

+62
-7
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"dependencies": {
5151
"@google-cloud/common": "^4.0.0",
5252
"@google-cloud/paginator": "^4.0.0",
53+
"@google-cloud/precise-date": "^3.0.1",
5354
"@google-cloud/promisify": "^3.0.0",
5455
"arrify": "^2.0.1",
5556
"big.js": "^6.0.0",

src/bigquery.ts

+40-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from '@google-cloud/common';
2424
import {paginator, ResourceStream} from '@google-cloud/paginator';
2525
import {promisifyAll} from '@google-cloud/promisify';
26+
import {PreciseDate} from '@google-cloud/precise-date';
2627
import arrify = require('arrify');
2728
import {Big} from 'big.js';
2829
import * as extend from 'extend';
@@ -585,7 +586,7 @@ export class BigQuery extends Service {
585586
break;
586587
}
587588
case 'TIMESTAMP': {
588-
value = BigQuery.timestamp(new Date(value * 1000));
589+
value = BigQuery.timestamp(value);
589590
break;
590591
}
591592
case 'GEOGRAPHY': {
@@ -844,11 +845,11 @@ export class BigQuery extends Service {
844845
* const timestamp = bigquery.timestamp(new Date());
845846
* ```
846847
*/
847-
static timestamp(value: Date | string) {
848+
static timestamp(value: Date | PreciseDate | string | number) {
848849
return new BigQueryTimestamp(value);
849850
}
850851

851-
timestamp(value: Date | string) {
852+
timestamp(value: Date | PreciseDate | string | number) {
852853
return BigQuery.timestamp(value);
853854
}
854855

@@ -2125,8 +2126,42 @@ export class Geography {
21252126
*/
21262127
export class BigQueryTimestamp {
21272128
value: string;
2128-
constructor(value: Date | string) {
2129-
this.value = new Date(value).toJSON();
2129+
constructor(value: Date | PreciseDate | string | number) {
2130+
let pd: PreciseDate;
2131+
if (value instanceof PreciseDate) {
2132+
pd = value;
2133+
} else if (value instanceof Date) {
2134+
pd = new PreciseDate(value);
2135+
} else if (typeof value === 'string') {
2136+
if (/^\d{4}-\d{1,2}-\d{1,2}/.test(value)) {
2137+
pd = new PreciseDate(value);
2138+
} else {
2139+
const floatValue = Number.parseFloat(value);
2140+
if (!Number.isNaN(floatValue)) {
2141+
pd = this.fromFloatValue_(floatValue);
2142+
} else {
2143+
pd = new PreciseDate(value);
2144+
}
2145+
}
2146+
} else {
2147+
pd = this.fromFloatValue_(value);
2148+
}
2149+
// to keep backward compatibility, only converts with microsecond
2150+
// precision if needed.
2151+
if (pd.getMicroseconds() > 0) {
2152+
this.value = pd.toISOString();
2153+
} else {
2154+
this.value = new Date(pd.getTime()).toJSON();
2155+
}
2156+
}
2157+
2158+
fromFloatValue_(value: number): PreciseDate {
2159+
const secs = Math.trunc(value);
2160+
// Timestamps in BigQuery have microsecond precision, so we must
2161+
// return a round number of microseconds.
2162+
const micros = Math.trunc((value - secs) * 1e6 + 0.5);
2163+
const pd = new PreciseDate([secs, micros * 1000]);
2164+
return pd;
21302165
}
21312166
}
21322167

test/bigquery.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
TableField,
4343
} from '../src';
4444
import {SinonStub} from 'sinon';
45+
import {PreciseDate} from '@google-cloud/precise-date';
4546

4647
const fakeUuid = extend(true, {}, uuid);
4748

@@ -453,7 +454,7 @@ describe('BigQuery', () => {
453454
f: [
454455
{v: '3'},
455456
{v: 'Milo'},
456-
{v: String(now.valueOf() / 1000)},
457+
{v: now.valueOf() * 1000},
457458
{v: 'false'},
458459
{v: 'true'},
459460
{v: '5.222330009847'},
@@ -505,7 +506,7 @@ describe('BigQuery', () => {
505506
id: 3,
506507
name: 'Milo',
507508
dob: {
508-
input: now,
509+
input: now.valueOf() * 1000,
509510
type: 'fakeTimestamp',
510511
},
511512
has_claws: false,
@@ -803,8 +804,11 @@ describe('BigQuery', () => {
803804

804805
describe('timestamp', () => {
805806
const INPUT_STRING = '2016-12-06T12:00:00.000Z';
807+
const INPUT_STRING_MICROS = '2016-12-06T12:00:00.123456Z';
806808
const INPUT_DATE = new Date(INPUT_STRING);
809+
const INPUT_PRECISE_DATE = new PreciseDate(INPUT_STRING_MICROS);
807810
const EXPECTED_VALUE = INPUT_DATE.toJSON();
811+
const EXPECTED_VALUE_MICROS = INPUT_PRECISE_DATE.toISOString();
808812

809813
// tslint:disable-next-line ban
810814
it.skip('should expose static and instance constructors', () => {
@@ -822,15 +826,30 @@ describe('BigQuery', () => {
822826
assert.strictEqual(timestamp.constructor.name, 'BigQueryTimestamp');
823827
});
824828

829+
it('should accept a NaN', () => {
830+
const timestamp = bq.timestamp(NaN);
831+
assert.strictEqual(timestamp.value, null);
832+
});
833+
825834
it('should accept a string', () => {
826835
const timestamp = bq.timestamp(INPUT_STRING);
827836
assert.strictEqual(timestamp.value, EXPECTED_VALUE);
828837
});
829838

839+
it('should accept a string with microseconds', () => {
840+
const timestamp = bq.timestamp(INPUT_STRING_MICROS);
841+
assert.strictEqual(timestamp.value, EXPECTED_VALUE_MICROS);
842+
});
843+
830844
it('should accept a Date object', () => {
831845
const timestamp = bq.timestamp(INPUT_DATE);
832846
assert.strictEqual(timestamp.value, EXPECTED_VALUE);
833847
});
848+
849+
it('should accept a PreciseDate object', () => {
850+
const timestamp = bq.timestamp(INPUT_PRECISE_DATE);
851+
assert.strictEqual(timestamp.value, EXPECTED_VALUE_MICROS);
852+
});
834853
});
835854

836855
describe('geography', () => {

0 commit comments

Comments
 (0)