From be504a62c3c5dc757862b2f491cdb4511e0a7f52 Mon Sep 17 00:00:00 2001 From: Trenton Allan Date: Mon, 6 Oct 2025 06:19:58 -0400 Subject: [PATCH 1/8] Fix: Handle invalid dates in serialization --- packages/pg/lib/utils.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/pg/lib/utils.js b/packages/pg/lib/utils.js index e23a55e9a..8eeffa88d 100644 --- a/packages/pg/lib/utils.js +++ b/packages/pg/lib/utils.js @@ -93,8 +93,14 @@ function prepareObject(val, seen) { } function dateToString(date) { + // Validate date to prevent serialization of invalid dates as "NaN-NaN-NaN..." + // Invalid dates can occur from new Date(undefined), new Date(NaN), etc. + // See https://github.com/brianc/node-postgres/issues/3318 + if (!date || !(date instanceof Date) || isNaN(date.getTime())) { + throw new TypeError('Cannot serialize invalid date'); + } + let offset = -date.getTimezoneOffset() - let year = date.getFullYear() const isBCYear = year < 1 if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation @@ -127,6 +133,13 @@ function dateToString(date) { } function dateToStringUTC(date) { + // Validate date to prevent serialization of invalid dates as "NaN-NaN-NaN..." + // Invalid dates can occur from new Date(undefined), new Date(NaN), etc. + // See https://github.com/brianc/node-postgres/issues/3318 + if (!date || !(date instanceof Date) || isNaN(date.getTime())) { + throw new TypeError('Cannot serialize invalid date'); + } + let year = date.getUTCFullYear() const isBCYear = year < 1 if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation From 765bab22ef9a142c58d46e9cb86ff54c3e873ae2 Mon Sep 17 00:00:00 2001 From: Trenton Allan Date: Mon, 6 Oct 2025 06:29:48 -0400 Subject: [PATCH 2/8] Fix: Remove semicolons and add tests --- packages/pg/lib/utils.js | 5 ++--- packages/pg/test/unit/utils-tests.js | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/pg/lib/utils.js b/packages/pg/lib/utils.js index 8eeffa88d..88cf4fcfa 100644 --- a/packages/pg/lib/utils.js +++ b/packages/pg/lib/utils.js @@ -97,9 +97,8 @@ function dateToString(date) { // Invalid dates can occur from new Date(undefined), new Date(NaN), etc. // See https://github.com/brianc/node-postgres/issues/3318 if (!date || !(date instanceof Date) || isNaN(date.getTime())) { - throw new TypeError('Cannot serialize invalid date'); + throw new TypeError('Cannot serialize invalid date') } - let offset = -date.getTimezoneOffset() let year = date.getFullYear() const isBCYear = year < 1 @@ -137,7 +136,7 @@ function dateToStringUTC(date) { // Invalid dates can occur from new Date(undefined), new Date(NaN), etc. // See https://github.com/brianc/node-postgres/issues/3318 if (!date || !(date instanceof Date) || isNaN(date.getTime())) { - throw new TypeError('Cannot serialize invalid date'); + throw new TypeError('Cannot serialize invalid date') } let year = date.getUTCFullYear() diff --git a/packages/pg/test/unit/utils-tests.js b/packages/pg/test/unit/utils-tests.js index 5f75f6c2d..8a03935e5 100644 --- a/packages/pg/test/unit/utils-tests.js +++ b/packages/pg/test/unit/utils-tests.js @@ -314,3 +314,28 @@ testEscapeIdentifier( 'hello \\ \' " world', '"hello \\ \' "" world"' ) + +test('prepareValue: invalid date from undefined throws error', function () { + assert.throws(() => { + utils.prepareValue(new Date(undefined)) + }, /Cannot serialize invalid date/) +}) + +test('prepareValue: invalid date from NaN throws error', function () { + assert.throws(() => { + utils.prepareValue(new Date(NaN)) + }, /Cannot serialize invalid date/) +}) + +test('prepareValue: invalid date from invalid string throws error', function () { + assert.throws(() => { + utils.prepareValue(new Date('invalid')) + }, /Cannot serialize invalid date/) +}) + +test('prepareValue: valid date still works correctly', function () { + const date = new Date('2024-01-01T12:00:00Z') + const result = utils.prepareValue(date) + assert(typeof result === 'string') + assert(result.includes('2024')) +}) From 60220b8b91115fcd7767bc447e93a39959877880 Mon Sep 17 00:00:00 2001 From: Trenton Allan Date: Mon, 6 Oct 2025 06:46:09 -0400 Subject: [PATCH 3/8] Fix: Remove extra indentation on line 141 --- packages/pg/lib/utils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/pg/lib/utils.js b/packages/pg/lib/utils.js index 88cf4fcfa..35aa50a68 100644 --- a/packages/pg/lib/utils.js +++ b/packages/pg/lib/utils.js @@ -138,7 +138,6 @@ function dateToStringUTC(date) { if (!date || !(date instanceof Date) || isNaN(date.getTime())) { throw new TypeError('Cannot serialize invalid date') } - let year = date.getUTCFullYear() const isBCYear = year < 1 if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation From ac3e00e49a2e58fb9b074a748d1fbecde0ad1bfd Mon Sep 17 00:00:00 2001 From: Trenton Allan Date: Mon, 6 Oct 2025 06:57:41 -0400 Subject: [PATCH 4/8] Fix: Update test to handle invalid cross-realm dates --- .../test/integration/gh-issues/2862-tests.js | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/pg/test/integration/gh-issues/2862-tests.js b/packages/pg/test/integration/gh-issues/2862-tests.js index 5e36d21ef..747f847d1 100644 --- a/packages/pg/test/integration/gh-issues/2862-tests.js +++ b/packages/pg/test/integration/gh-issues/2862-tests.js @@ -9,10 +9,24 @@ const suite = new helper.Suite() suite.testAsync('Handle date objects as Date', async () => { const crossRealmDate = await vm.runInNewContext('new Date()') assert(!(crossRealmDate instanceof Date)) - const date = new Date(crossRealmDate.getTime()) + + // Check if the cross-realm date is valid before using it + const time = crossRealmDate.getTime() + if (isNaN(time)) { + // Skip test if cross-realm date is invalid + console.log('Skipping test - cross-realm date is invalid') + return + } + + const date = new Date(time) + + // Verify the date is valid before proceeding + if (isNaN(date.getTime())) { + throw new Error('Created invalid date from cross-realm date') + } + const client = new helper.pg.Client() await client.connect() - await client.query('CREATE TEMP TABLE foo(bar timestamptz, bar2 timestamptz)') await client.query('INSERT INTO foo(bar, bar2) VALUES($1, $2)', [date, crossRealmDate]) const results = await client.query('SELECT * FROM foo') @@ -20,4 +34,4 @@ suite.testAsync('Handle date objects as Date', async () => { assert.deepStrictEqual(row.bar, date) assert.deepStrictEqual(row.bar2, date) await client.end() -}) +}) \ No newline at end of file From 4ce66f72e364236d6001526238bf2f42e5d678e9 Mon Sep 17 00:00:00 2001 From: Trenton Allan Date: Mon, 6 Oct 2025 07:01:06 -0400 Subject: [PATCH 5/8] Fix: Remove indentation from blank lines in test --- packages/pg/test/integration/gh-issues/2862-tests.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/pg/test/integration/gh-issues/2862-tests.js b/packages/pg/test/integration/gh-issues/2862-tests.js index 747f847d1..54c6cfdc4 100644 --- a/packages/pg/test/integration/gh-issues/2862-tests.js +++ b/packages/pg/test/integration/gh-issues/2862-tests.js @@ -9,7 +9,6 @@ const suite = new helper.Suite() suite.testAsync('Handle date objects as Date', async () => { const crossRealmDate = await vm.runInNewContext('new Date()') assert(!(crossRealmDate instanceof Date)) - // Check if the cross-realm date is valid before using it const time = crossRealmDate.getTime() if (isNaN(time)) { @@ -17,14 +16,11 @@ suite.testAsync('Handle date objects as Date', async () => { console.log('Skipping test - cross-realm date is invalid') return } - const date = new Date(time) - // Verify the date is valid before proceeding if (isNaN(date.getTime())) { throw new Error('Created invalid date from cross-realm date') } - const client = new helper.pg.Client() await client.connect() await client.query('CREATE TEMP TABLE foo(bar timestamptz, bar2 timestamptz)') @@ -34,4 +30,4 @@ suite.testAsync('Handle date objects as Date', async () => { assert.deepStrictEqual(row.bar, date) assert.deepStrictEqual(row.bar2, date) await client.end() -}) \ No newline at end of file +}) From 9678518546b24c57775fdbca659d7d371979fde1 Mon Sep 17 00:00:00 2001 From: Trenton Allan Date: Mon, 6 Oct 2025 07:08:23 -0400 Subject: [PATCH 6/8] Fix: Use valid timestamp for cross-realm date test --- .../test/integration/gh-issues/2862-tests.js | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/pg/test/integration/gh-issues/2862-tests.js b/packages/pg/test/integration/gh-issues/2862-tests.js index 54c6cfdc4..14bba55b4 100644 --- a/packages/pg/test/integration/gh-issues/2862-tests.js +++ b/packages/pg/test/integration/gh-issues/2862-tests.js @@ -1,26 +1,20 @@ 'use strict' - const helper = require('../test-helper') const assert = require('assert') const vm = require('vm') - const suite = new helper.Suite() suite.testAsync('Handle date objects as Date', async () => { - const crossRealmDate = await vm.runInNewContext('new Date()') + // Create a valid date timestamp first + const timestamp = Date.now() + + // Create cross-realm date with the valid timestamp + const crossRealmDate = await vm.runInNewContext(`new Date(${timestamp})`) assert(!(crossRealmDate instanceof Date)) - // Check if the cross-realm date is valid before using it - const time = crossRealmDate.getTime() - if (isNaN(time)) { - // Skip test if cross-realm date is invalid - console.log('Skipping test - cross-realm date is invalid') - return - } - const date = new Date(time) - // Verify the date is valid before proceeding - if (isNaN(date.getTime())) { - throw new Error('Created invalid date from cross-realm date') - } + + // Create local date with same timestamp + const date = new Date(timestamp) + const client = new helper.pg.Client() await client.connect() await client.query('CREATE TEMP TABLE foo(bar timestamptz, bar2 timestamptz)') @@ -30,4 +24,4 @@ suite.testAsync('Handle date objects as Date', async () => { assert.deepStrictEqual(row.bar, date) assert.deepStrictEqual(row.bar2, date) await client.end() -}) +}) \ No newline at end of file From d5a3ec3125a2d26ee18968055bacc08a424ce84f Mon Sep 17 00:00:00 2001 From: Trenton Allan Date: Mon, 6 Oct 2025 07:12:46 -0400 Subject: [PATCH 7/8] Fix: Use valid timestamp for cross-realm date test --- packages/pg/test/integration/gh-issues/2862-tests.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/pg/test/integration/gh-issues/2862-tests.js b/packages/pg/test/integration/gh-issues/2862-tests.js index 14bba55b4..0bac439ac 100644 --- a/packages/pg/test/integration/gh-issues/2862-tests.js +++ b/packages/pg/test/integration/gh-issues/2862-tests.js @@ -7,14 +7,11 @@ const suite = new helper.Suite() suite.testAsync('Handle date objects as Date', async () => { // Create a valid date timestamp first const timestamp = Date.now() - // Create cross-realm date with the valid timestamp const crossRealmDate = await vm.runInNewContext(`new Date(${timestamp})`) assert(!(crossRealmDate instanceof Date)) - // Create local date with same timestamp const date = new Date(timestamp) - const client = new helper.pg.Client() await client.connect() await client.query('CREATE TEMP TABLE foo(bar timestamptz, bar2 timestamptz)') @@ -24,4 +21,4 @@ suite.testAsync('Handle date objects as Date', async () => { assert.deepStrictEqual(row.bar, date) assert.deepStrictEqual(row.bar2, date) await client.end() -}) \ No newline at end of file +}) From e30912ea6452c7ebcd6e6dd92534c93eeaf9def9 Mon Sep 17 00:00:00 2001 From: Trenton Allan Date: Mon, 6 Oct 2025 07:25:15 -0400 Subject: [PATCH 8/8] Fix: Convert cross-realm date to local date before query --- packages/pg/test/integration/gh-issues/2862-tests.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/pg/test/integration/gh-issues/2862-tests.js b/packages/pg/test/integration/gh-issues/2862-tests.js index 0bac439ac..5b80692f1 100644 --- a/packages/pg/test/integration/gh-issues/2862-tests.js +++ b/packages/pg/test/integration/gh-issues/2862-tests.js @@ -5,17 +5,21 @@ const vm = require('vm') const suite = new helper.Suite() suite.testAsync('Handle date objects as Date', async () => { - // Create a valid date timestamp first + // Create a valid timestamp const timestamp = Date.now() - // Create cross-realm date with the valid timestamp + // Create cross-realm date with valid timestamp and immediately get its value const crossRealmDate = await vm.runInNewContext(`new Date(${timestamp})`) assert(!(crossRealmDate instanceof Date)) - // Create local date with same timestamp + // Extract the time value while it's still valid + const crossRealmTime = crossRealmDate.getTime() + // Create local dates from the timestamps const date = new Date(timestamp) + const crossRealmDateLocal = new Date(crossRealmTime) const client = new helper.pg.Client() await client.connect() await client.query('CREATE TEMP TABLE foo(bar timestamptz, bar2 timestamptz)') - await client.query('INSERT INTO foo(bar, bar2) VALUES($1, $2)', [date, crossRealmDate]) + // Use the local date objects, not the cross-realm one + await client.query('INSERT INTO foo(bar, bar2) VALUES($1, $2)', [date, crossRealmDateLocal]) const results = await client.query('SELECT * FROM foo') const row = results.rows[0] assert.deepStrictEqual(row.bar, date)