← All notes

Notes · 2026-09-15

psqlodbc hands a timestamptz over in session time, and the offset is gone

Through psqlodbc, a timestamp with time zone arrives as the session's wall-clock time without its offset, so on a server whose zone is not UTC every zoned value was shifted, on read and on ingest; adbcBridge 0.1.3 puts the session on UTC.

What a reader would see

On a PostgreSQL server whose session time zone is America/New_York, a column holding 2024-02-29 13:45:10+00 came back through adbcBridge 0.1.2 as 2024-02-29 08:45:10 labelled UTC, five hours early. Ingest was wrong the other way: an Arrow timestamp[ms, tz=UTC] of 13:45:10 landed in a timestamptz column as 2024-02-29 13:45:10-05, which is 18:45:10 UTC. On a server whose zone is UTC neither happens, and every container in the compatibility matrix runs UTC, which is why the defect went unseen until a macOS test cluster, whose initdb had taken the host's zone, showed it on 2026-09-15.

Why

psqlodbc parses the timestamptz text the server sends, offset included, into its own timestamp structure and renders it again for the client without the zone: what reaches a SQL_C_CHAR buffer is 2024-02-29 08:45:10.123, the session's wall-clock time. adbcBridge reads zoned columns as text precisely so that an offset can be honoured, and its parser does honour one when present; with none present it took the value as UTC, which was only true when the session zone was UTC. On the way in, a bound SQL_TYPE_TIMESTAMP parameter travels as a zone-less literal the server reads in the session zone, so a UTC wall-clock rendering of the instant was interpreted as local time.

What 0.1.3 does

On a psqlodbc connection the driver runs SET TIME ZONE 'UTC' once at connect, before anything is read or written, so the wall-clock time psqlodbc hands over is the instant and the literal it sends is read as UTC. The new database option adbc.odbc.utc_session (default true) keeps the server's own setting when set to false; while the option is on, session-dependent SQL such as now()::text or date_trunc on a timestamptz renders in UTC, which is the one visible consequence. A server that does not accept the statement is left as it is.

Two tests in the PostgreSQL CI job now put the database on America/New_York and check that the instant survives a read and a bulk ingest, and that the option switches the behaviour off. The validation suite on PostgreSQL is unchanged at 293 of 327, and all 14 PostgreSQL-wire entries of the compatibility matrix pass with the session change.

Where this comes from: the adbcBridge compatibility matrix runs one workload through every database's ODBC driver on Linux, macOS and Windows and records each failure with its first error; the row for this database is at https://adbcbridge.org/matrix/#postgres, and the fix is on file in the repository's pull request #113.