diff --git a/CHANGELOG.md b/CHANGELOG.md index f27fad05..29a1772d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://www.npmjs.com/package/@google-cloud/bigquery?activeTab=versions +## [7.5.1](https://github.com/googleapis/nodejs-bigquery/compare/v7.5.0...v7.5.1) (2024-03-14) + + +### Bug Fixes + +* Add better documentation around usage of BigQueryTimestamp class and .timestamp method. ([2b2c3e0](https://github.com/googleapis/nodejs-bigquery/commit/2b2c3e0b8e9c13d23aa54e7a85a861c61410bd4b)) +* BigQueryTimestamp should keep accepting floats [#1339](https://github.com/googleapis/nodejs-bigquery/issues/1339) ([2b2c3e0](https://github.com/googleapis/nodejs-bigquery/commit/2b2c3e0b8e9c13d23aa54e7a85a861c61410bd4b)) +* Restores BigQueryTimestamp behavior to accept a numeric value in the constructor representing epoch-seconds. The affected 7.5.0 version would parse a numeric value as epoch-microseconds. ([2b2c3e0](https://github.com/googleapis/nodejs-bigquery/commit/2b2c3e0b8e9c13d23aa54e7a85a861c61410bd4b)) + ## [7.5.0](https://github.com/googleapis/nodejs-bigquery/compare/v7.4.0...v7.5.0) (2024-02-21) diff --git a/package.json b/package.json index 2b7ab4a6..713c05e8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@google-cloud/bigquery", "description": "Google BigQuery Client Library for Node.js", - "version": "7.5.0", + "version": "7.5.1", "license": "Apache-2.0", "author": "Google LLC", "engines": { diff --git a/samples/package.json b/samples/package.json index e11c12c5..e4bd9e19 100644 --- a/samples/package.json +++ b/samples/package.json @@ -17,7 +17,7 @@ "fix": "gts fix" }, "dependencies": { - "@google-cloud/bigquery": "^7.5.0", + "@google-cloud/bigquery": "^7.5.1", "@google-cloud/storage": "^7.0.0", "google-auth-library": "^9.6.0", "readline-promise": "^1.0.4", diff --git a/src/bigquery.ts b/src/bigquery.ts index 0df263dd..0cfc686a 100644 --- a/src/bigquery.ts +++ b/src/bigquery.ts @@ -640,7 +640,8 @@ export class BigQuery extends Service { break; } case 'TIMESTAMP': { - value = BigQuery.timestamp(value); + const pd = new PreciseDate(BigInt(value) * BigInt(1000)); + value = BigQuery.timestamp(pd); break; } case 'GEOGRAPHY': { @@ -881,6 +882,10 @@ export class BigQuery extends Service { * A timestamp represents an absolute point in time, independent of any time * zone or convention such as Daylight Savings Time. * + * The recommended input here is a `Date` or `PreciseDate` class. + * If passing as a `string`, it should be Timestamp literals: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#timestamp_literals. + * When passing a `number` input, it should be epoch seconds in float representation. + * * @method BigQuery.timestamp * @param {Date|string} value The time. * @@ -890,12 +895,19 @@ export class BigQuery extends Service { * const timestamp = BigQuery.timestamp(new Date()); * ``` */ + static timestamp(value: Date | PreciseDate | string | number) { + return new BigQueryTimestamp(value); + } /** * A timestamp represents an absolute point in time, independent of any time * zone or convention such as Daylight Savings Time. * - * @param {Date|string} value The time. + * The recommended input here is a `Date` or `PreciseDate` class. + * If passing as a `string`, it should be Timestamp literals: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#timestamp_literals. + * When passing a `number` input, it should be epoch seconds in float representation. + * + * @param {Date|string|string|number} value The time. * * @example * ``` @@ -904,10 +916,6 @@ export class BigQuery extends Service { * const timestamp = bigquery.timestamp(new Date()); * ``` */ - static timestamp(value: Date | PreciseDate | string | number) { - return new BigQueryTimestamp(value); - } - timestamp(value: Date | PreciseDate | string | number) { return BigQuery.timestamp(value); } @@ -2204,6 +2212,11 @@ export class Geography { /** * Timestamp class for BigQuery. + * + * The recommended input here is a `Date` or `PreciseDate` class. + * If passing as a `string`, it should be Timestamp literals: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#timestamp_literals. + * When passing a `number` input, it should be epoch seconds in float representation. + * */ export class BigQueryTimestamp { value: string; @@ -2217,13 +2230,15 @@ export class BigQueryTimestamp { if (/^\d{4}-\d{1,2}-\d{1,2}/.test(value)) { pd = new PreciseDate(value); } else { - pd = new PreciseDate(BigInt(value) * BigInt(1000)); + const floatValue = Number.parseFloat(value); + if (!Number.isNaN(floatValue)) { + pd = this.fromFloatValue_(floatValue); + } else { + pd = new PreciseDate(value); + } } - } else if (value) { - pd = new PreciseDate(BigInt(value) * BigInt(1000)); } else { - // Nan or 0 - invalid dates - pd = new PreciseDate(value); + pd = this.fromFloatValue_(value); } // to keep backward compatibility, only converts with microsecond // precision if needed. @@ -2233,6 +2248,15 @@ 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/test/bigquery.ts b/test/bigquery.ts index 914980ac..8efa158d 100644 --- a/test/bigquery.ts +++ b/test/bigquery.ts @@ -471,7 +471,7 @@ describe('BigQuery', () => { f: [ {v: '3'}, {v: 'Milo'}, - {v: now.valueOf() * 1000}, + {v: now.valueOf() * 1000}, // int64 microseconds {v: 'false'}, {v: 'true'}, {v: '5.222330009847'}, @@ -523,7 +523,7 @@ describe('BigQuery', () => { id: 3, name: 'Milo', dob: { - input: now.valueOf() * 1000, + input: new PreciseDate(BigInt(now.valueOf()) * BigInt(1_000_000)), type: 'fakeTimestamp', }, has_claws: false, @@ -850,10 +850,8 @@ 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(); @@ -883,31 +881,21 @@ 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); }); + it('should accept a float number', () => { + const d = new Date(); + const f = d.valueOf() / 1000; // float seconds + let timestamp = bq.timestamp(f); + assert.strictEqual(timestamp.value, d.toJSON()); + + timestamp = bq.timestamp(f.toString()); + assert.strictEqual(timestamp.value, d.toJSON()); + }); + it('should accept a Date object', () => { const timestamp = bq.timestamp(INPUT_DATE); assert.strictEqual(timestamp.value, EXPECTED_VALUE);