[go: up one dir, main page]

Skip to content

Commit

Permalink
fix: BigQueryTimestamp should keep accepting floats (#1339)
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarowolfx committed Mar 13, 2024
1 parent 024b1f6 commit 2b2c3e0
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 35 deletions.
46 changes: 35 additions & 11 deletions src/bigquery.ts
Expand Up @@ -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': {
Expand Down Expand Up @@ -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.
*
Expand All @@ -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
* ```
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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;
}
}

/**
Expand Down
36 changes: 12 additions & 24 deletions test/bigquery.ts
Expand Up @@ -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'},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 2b2c3e0

Please sign in to comment.