diff --git a/src/bigquery.ts b/src/bigquery.ts index f3e14113..0df263dd 100644 --- a/src/bigquery.ts +++ b/src/bigquery.ts @@ -2217,15 +2217,13 @@ export class BigQueryTimestamp { if (/^\d{4}-\d{1,2}-\d{1,2}/.test(value)) { pd = new PreciseDate(value); } else { - const floatValue = Number.parseFloat(value); - if (!Number.isNaN(floatValue)) { - pd = this.fromFloatValue_(floatValue); - } else { - pd = new PreciseDate(value); - } + pd = new PreciseDate(BigInt(value) * BigInt(1000)); } + } else if (value) { + pd = new PreciseDate(BigInt(value) * BigInt(1000)); } else { - pd = this.fromFloatValue_(value); + // Nan or 0 - invalid dates + pd = new PreciseDate(value); } // to keep backward compatibility, only converts with microsecond // precision if needed. @@ -2235,15 +2233,6 @@ export class BigQueryTimestamp { this.value = new Date(pd.getTime()).toJSON(); } } - - fromFloatValue_(value: number): PreciseDate { - const secs = Math.trunc(value); - // Timestamps in BigQuery have microsecond precision, so we must - // return a round number of microseconds. - const micros = Math.trunc((value - secs) * 1e6 + 0.5); - const pd = new PreciseDate([secs, micros * 1000]); - return pd; - } } /** diff --git a/src/job.ts b/src/job.ts index fd6a1481..5da91920 100644 --- a/src/job.ts +++ b/src/job.ts @@ -529,10 +529,10 @@ class Job extends Operation { typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; - const qs = extend( { location: this.location, + 'formatOptions.useInt64Timestamp': true, }, options ); diff --git a/src/table.ts b/src/table.ts index be19d18a..4dfe0792 100644 --- a/src/table.ts +++ b/src/table.ts @@ -1848,10 +1848,17 @@ class Table extends ServiceObject { callback!(null, rows, nextQuery, resp); }; + const qs = extend( + { + 'formatOptions.useInt64Timestamp': true, + }, + options + ); + this.request( { uri: '/data', - qs: options, + qs, }, (err, resp) => { if (err) { @@ -1860,7 +1867,7 @@ class Table extends ServiceObject { } let nextQuery: GetRowsOptions | null = null; if (resp.pageToken) { - nextQuery = Object.assign({}, options, { + nextQuery = Object.assign({}, qs, { pageToken: resp.pageToken, }); } diff --git a/test/bigquery.ts b/test/bigquery.ts index 34669d1b..914980ac 100644 --- a/test/bigquery.ts +++ b/test/bigquery.ts @@ -850,8 +850,10 @@ describe('BigQuery', () => { describe('timestamp', () => { const INPUT_STRING = '2016-12-06T12:00:00.000Z'; const INPUT_STRING_MICROS = '2016-12-06T12:00:00.123456Z'; + const INPUT_STRING_NEGATIVE = '1969-12-25T00:00:00.000Z'; const INPUT_DATE = new Date(INPUT_STRING); const INPUT_PRECISE_DATE = new PreciseDate(INPUT_STRING_MICROS); + const INPUT_PRECISE_NEGATIVE_DATE = new PreciseDate(INPUT_STRING_NEGATIVE); const EXPECTED_VALUE = INPUT_DATE.toJSON(); const EXPECTED_VALUE_MICROS = INPUT_PRECISE_DATE.toISOString(); @@ -881,6 +883,26 @@ describe('BigQuery', () => { assert.strictEqual(timestamp.value, EXPECTED_VALUE); }); + it('should accept a number in microseconds', () => { + let ms = INPUT_PRECISE_DATE.valueOf(); // milliseconds + let us = ms * 1000 + INPUT_PRECISE_DATE.getMicroseconds(); // microseconds + let timestamp = bq.timestamp(us); + assert.strictEqual(timestamp.value, EXPECTED_VALUE_MICROS); + + let usStr = `${us}`; + timestamp = bq.timestamp(usStr); + assert.strictEqual(timestamp.value, EXPECTED_VALUE_MICROS); + + ms = INPUT_PRECISE_NEGATIVE_DATE.valueOf(); + us = ms * 1000; + timestamp = bq.timestamp(us); + assert.strictEqual(timestamp.value, INPUT_STRING_NEGATIVE); + + usStr = `${us}`; + timestamp = bq.timestamp(usStr); + assert.strictEqual(timestamp.value, INPUT_STRING_NEGATIVE); + }); + it('should accept a string with microseconds', () => { const timestamp = bq.timestamp(INPUT_STRING_MICROS); assert.strictEqual(timestamp.value, EXPECTED_VALUE_MICROS); diff --git a/test/job.ts b/test/job.ts index 186939a9..cb777b62 100644 --- a/test/job.ts +++ b/test/job.ts @@ -238,7 +238,10 @@ describe('BigQuery/Job', () => { it('should optionally accept options', done => { const options = {a: 'b'}; - const expectedOptions = Object.assign({location: undefined}, options); + const expectedOptions = Object.assign( + {location: undefined, 'formatOptions.useInt64Timestamp': true}, + options + ); BIGQUERY.request = (reqOpts: DecorateRequestOptions) => { assert.deepStrictEqual(reqOpts.qs, expectedOptions); @@ -252,7 +255,10 @@ describe('BigQuery/Job', () => { const job = new Job(BIGQUERY, JOB_ID, {location: LOCATION}); BIGQUERY.request = (reqOpts: DecorateRequestOptions) => { - assert.deepStrictEqual(reqOpts.qs, {location: LOCATION}); + assert.deepStrictEqual(reqOpts.qs, { + location: LOCATION, + 'formatOptions.useInt64Timestamp': true, + }); done(); }; @@ -261,7 +267,11 @@ describe('BigQuery/Job', () => { it('should delete any cached jobs', done => { const options = {job: {}, a: 'b'}; - const expectedOptions = {location: undefined, a: 'b'}; + const expectedOptions = { + location: undefined, + a: 'b', + 'formatOptions.useInt64Timestamp': true, + }; BIGQUERY.request = (reqOpts: DecorateRequestOptions) => { assert.deepStrictEqual(reqOpts.qs, expectedOptions); @@ -340,7 +350,10 @@ describe('BigQuery/Job', () => { const mergedRows: Array<{}> = []; const options = {wrapIntegers: true}; - const expectedOptions = Object.assign({location: undefined}); + const expectedOptions = Object.assign({ + location: undefined, + 'formatOptions.useInt64Timestamp': true, + }); BIGQUERY.request = (reqOpts: DecorateRequestOptions) => { assert.deepStrictEqual(reqOpts.qs, expectedOptions); @@ -368,7 +381,10 @@ describe('BigQuery/Job', () => { const mergedRows: Array<{}> = []; const options = {parseJSON: true}; - const expectedOptions = Object.assign({location: undefined}); + const expectedOptions = Object.assign({ + location: undefined, + 'formatOptions.useInt64Timestamp': true, + }); BIGQUERY.request = (reqOpts: DecorateRequestOptions) => { assert.deepStrictEqual(reqOpts.qs, expectedOptions); diff --git a/test/table.ts b/test/table.ts index bfafcae6..11322410 100644 --- a/test/table.ts +++ b/test/table.ts @@ -1979,7 +1979,10 @@ describe('BigQuery/Table', () => { table.request = (reqOpts: DecorateRequestOptions, callback: Function) => { assert.strictEqual(reqOpts.uri, '/data'); - assert.strictEqual(reqOpts.qs, options); + assert.deepStrictEqual(reqOpts.qs, { + ...options, + 'formatOptions.useInt64Timestamp': true, + }); callback(null, {}); }; @@ -2129,7 +2132,10 @@ describe('BigQuery/Table', () => { table.metadata = {schema: {}}; table.request = (reqOpts: DecorateRequestOptions, callback: Function) => { - callback(null, {pageToken}); + callback(null, { + 'formatOptions.useInt64Timestamp': true, + pageToken, + }); }; table.getRows(options, (err: Error, rows: {}, nextQuery: {}) => { @@ -2137,6 +2143,7 @@ describe('BigQuery/Table', () => { assert.deepStrictEqual(nextQuery, { a: 'b', c: 'd', + 'formatOptions.useInt64Timestamp': true, pageToken, }); // Original object isn't affected. @@ -2257,7 +2264,9 @@ describe('BigQuery/Table', () => { const merged = [{name: 'stephen'}]; table.request = (reqOpts: DecorateRequestOptions, callback: Function) => { - assert.deepStrictEqual(reqOpts.qs, {}); + assert.deepStrictEqual(reqOpts.qs, { + 'formatOptions.useInt64Timestamp': true, + }); callback(null, {}); }; @@ -2279,7 +2288,9 @@ describe('BigQuery/Table', () => { const merged = [{name: 'stephen'}]; table.request = (reqOpts: DecorateRequestOptions, callback: Function) => { - assert.deepStrictEqual(reqOpts.qs, {}); + assert.deepStrictEqual(reqOpts.qs, { + 'formatOptions.useInt64Timestamp': true, + }); callback(null, {}); };