You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Neither — it silently returns the wrong value. No exception is raised.
What error(s) or behavior are you seeing?
For an offset-basedTIMESTAMP WITH TIME ZONE value, connection.fetch_df_all() (the Arrow dataframe path) returns an instant equal to wall_clock_time + zone_offset, instead of normalizing to UTC. The regular cursor.fetchall() / fetchone() path is correct.
The offset is applied per value, in the wrong direction, which is what makes the Thick-mode result neither UTC nor wall-clock:
Literal
fetch_df_all (Thick)
Expected UTC instant
19:00:00 +08:00
2026-06-02 03:00:00 (+8h)
2026-06-01 11:00:00
19:00:00 -05:00
2026-06-01 14:00:00 (-5h)
2026-06-02 00:00:00
So the same value comes back differently depending on its offset, confirming the offset is being added to the wall-clock time rather than subtracted to obtain UTC.
Notes:
This looks related to Fetching DATE/TIMESTAMP as DF converts to UTC TZ #499 (DATE/TIMESTAMP DF→UTC conversion, fixed in 3.2.0), but that fix did not cover TIMESTAMP WITH TIME ZONE: the bug is still present on 3.3.0.
Thin mode does not reproduce — it returns the wall-clock time (offset dropped). Only Thick mode double-applies the offset, so this appears to be in the Thick-mode Arrow conversion for DB_TYPE_TIMESTAMP_TZ / DB_TYPE_TIMESTAMP_LTZ.
TIMESTAMP WITH LOCAL TIME ZONE is affected the same way.
Does your application call init_oracle_client()?
Yes — the bug reproduces in Thick mode. In Thin mode it does not (see table above).
Include a runnable Python script that shows the problem.
No table is required — the script uses dual and TIMESTAMP literals:
importoracledbimportpyarrowaspa# Comment out init_oracle_client() to run in Thin mode, where the bug does NOT# reproduce (Thin returns the wall-clock time instead).oracledb.init_oracle_client() # Thick modeconn=oracledb.connect(user="...", password="...", dsn="host:port/SERVICE")
sql= (
"SELECT TIMESTAMP '2026-06-01 19:00:00 +08:00' AS t_plus8, ""TIMESTAMP '2026-06-01 19:00:00 -05:00' AS t_minus5 FROM dual"
)
withconn.cursor() ascur:
cur.execute(sql)
print("fetchone ->", cur.fetchone())
# Thick: (datetime(2026, 6, 1, 19, 0), datetime(2026, 6, 1, 19, 0))odf=conn.fetch_df_all(sql)
tbl=pa.Table.from_arrays(odf.column_arrays(), names=odf.column_names())
print("fetch_df_all ->", tbl.to_pylist())
# Thick: [{'T_PLUS8': 2026-06-02 03:00:00, 'T_MINUS5': 2026-06-01 14:00:00}] <-- wall + offset# Thin : [{'T_PLUS8': 2026-06-01 19:00:00, 'T_MINUS5': 2026-06-01 19:00:00}] <-- wall-clock# Expected UTC: 2026-06-01 11:00:00 and 2026-06-02 00:00:00
Expected: fetch_df_all should return the same instant the regular fetch represents (ideally a tz-aware / UTC-normalized Arrow timestamp), not wall_clock + offset.
Neither — it silently returns the wrong value. No exception is raised.
For an offset-based
TIMESTAMP WITH TIME ZONEvalue,connection.fetch_df_all()(the Arrow dataframe path) returns an instant equal towall_clock_time + zone_offset, instead of normalizing to UTC. The regularcursor.fetchall()/fetchone()path is correct.For
TIMESTAMP '2026-06-01 19:00:00 +08:00':cursor.fetchone()2026-06-01 19:00:00fetch_df_all()Thick2026-06-02 03:00:0019:00 + 08:00fetch_df_all()Thin2026-06-01 19:00:00The offset is applied per value, in the wrong direction, which is what makes the Thick-mode result neither UTC nor wall-clock:
fetch_df_all(Thick)19:00:00 +08:002026-06-02 03:00:00(+8h)2026-06-01 11:00:0019:00:00 -05:002026-06-01 14:00:00(-5h)2026-06-02 00:00:00So the same value comes back differently depending on its offset, confirming the offset is being added to the wall-clock time rather than subtracted to obtain UTC.
Notes:
TIMESTAMP WITH TIME ZONE: the bug is still present on 3.3.0.DB_TYPE_TIMESTAMP_TZ/DB_TYPE_TIMESTAMP_LTZ.TIMESTAMP WITH LOCAL TIME ZONEis affected the same way.init_oracle_client()?Yes — the bug reproduces in Thick mode. In Thin mode it does not (see table above).
No table is required — the script uses
dualandTIMESTAMPliterals:Expected:
fetch_df_allshould return the same instant the regular fetch represents (ideally a tz-aware / UTC-normalized Arrowtimestamp), notwall_clock + offset.