Of course! Working with Oracle dates in Python is a common task, especially when interacting with databases. The key is to handle the conversion between Python's datetime objects and Oracle's DATE or TIMESTAMP data types correctly.

Here’s a comprehensive guide covering the most common scenarios, using the popular cx_Oracle driver.
Prerequisites: Install the Oracle Driver
First, you need to install the Oracle client library and the Python driver. The easiest way is to use the pre-compiled cx_Oracle wheels.
# Install the Python driver pip install cx_Oracle
The cx_Oracle package will automatically attempt to find the Oracle client libraries. If it can't find them, you'll need to install them manually and set the LD_LIBRARY_PATH (Linux/macOS) or PATH (Windows) environment variable to point to the Oracle client's bin directory.
Scenario 1: Inserting a Python datetime into an Oracle Table
This is the most frequent operation. You have a Python datetime object and want to save it to an Oracle DATE column.

The Problem by Default
If you try to pass a Python datetime object directly to cursor.execute(), you'll likely get a TypeError. Oracle expects its own date format.
import cx_Oracle
from datetime import datetime
# --- Setup (replace with your actual connection details) ---
# Ensure your TNS_ADMIN environment variable is set, or provide the dsn
try:
conn = cx_Oracle.connect(user="your_username", password="your_password", dsn="your_dsn")
print("Successfully connected to Oracle Database")
except cx_Oracle.DatabaseError as e:
print(f"Error connecting to Oracle: {e}")
exit()
# --- Problematic Code ---
try:
cursor = conn.cursor()
my_date = datetime(2025, 10, 27, 10, 30, 0) # A Python datetime object
# This will FAIL with a TypeError
# cursor.execute("INSERT INTO my_table (id, event_date) VALUES (1, :1)", (my_date,))
# print("Insert successful (this won't run)")
except Exception as e:
print(f"An error occurred: {e}")
The Solution: Use cursor.setinputsizes()
The standard and most robust way to handle this is to tell cx_Oracle what data type to expect for your variable using cursor.setinputsizes().
import cx_Oracle
from datetime import datetime
# Assume 'conn' is your active connection from the previous example
cursor = conn.cursor()
# 1. Create a table for the example (if it doesn't exist)
cursor.execute("""
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE my_date_table';
EXCEPTION
WHEN OTHERS THEN NULL;
END;
""")
cursor.execute("""
CREATE TABLE my_date_table (
id NUMBER PRIMARY KEY,
event_date DATE,
description VARCHAR2(100)
)
""")
# 2. Define your Python datetime object
my_date = datetime(2025, 10, 27, 10, 30, 0)
# 3. Tell the cursor to expect an Oracle DATE type for the second variable
# This is the key step!
cursor.setinputsizes(cx_Oracle.DATETIME)
# 4. Now, execute the insert. It will work perfectly.
try:
cursor.execute(
"INSERT INTO my_date_table (id, event_date, description) VALUES (:1, :2, :3)",
(101, my_date, "Python datetime inserted")
)
conn.commit()
print("Successfully inserted datetime into Oracle.")
except cx_Oracle.DatabaseError as e:
print(f"Error inserting: {e}")
conn.rollback()
cursor.close()
Scenario 2: Fetching an Oracle Date into a Python datetime
When you query an Oracle DATE column, cx_Oracle automatically converts it into a Python datetime object. This is usually what you want.
# Assume the data from the previous example is in the table
cursor = conn.cursor()
try:
cursor.execute("SELECT id, event_date, description FROM my_date_table WHERE id = 101")
# Fetch one row
row = cursor.fetchone()
if row:
id_val, event_date, desc = row
# 'event_date' is now a Python datetime object
print(f"Fetched ID: {id_val}")
print(f"Fetched Date Type: {type(event_date)}") # <class 'datetime.datetime'>
print(f"Fetched Date Value: {event_date}")
print(f"Fetched Description: {desc}")
# You can now use it like any other Python datetime
print(f"Formatted Date: {event_date.strftime('%Y-%m-%d %H:%M:%S')}")
except cx_Oracle.DatabaseError as e:
print(f"Error fetching: {e}")
cursor.close()
Scenario 3: Working with Oracle TIMESTAMP Data Types
Oracle's TIMESTAMP data type is more precise than DATE (it can include fractional seconds and time zone information). cx_Oracle handles this gracefully.
Inserting a datetime into a TIMESTAMP column
The process is almost identical. You just need to specify the correct type.
# Assume a table with a TIMESTAMP column
cursor.execute("""
CREATE TABLE my_timestamp_table (
id NUMBER PRIMARY KEY,
event_ts TIMESTAMP(6), -- TIMESTAMP with 6 fractional digits
description VARCHAR2(100)
)
""")
my_datetime = datetime(2025, 10, 27, 10, 30, 45, 123456) # Includes microseconds
# Tell the cursor to expect an Oracle TIMESTAMP type
cursor.setinputsizes(cx_Oracle.TIMESTAMP)
cursor.execute(
"INSERT INTO my_timestamp_table (id, event_ts, description) VALUES (:1, :2, :3)",
(201, my_datetime, "Python datetime with microseconds")
)
conn.commit()
print("Successfully inserted datetime into TIMESTAMP column.")
Fetching a TIMESTAMP into a Python datetime
When you fetch a TIMESTAMP, cx_Oracle converts it into a Python datetime object. The microseconds will be preserved.
cursor.execute("SELECT id, event_ts FROM my_timestamp_table WHERE id = 201")
row = cursor.fetchone()
if row:
id_val, event_ts = row
print(f"Fetched ID: {id_val}")
print(f"Fetched Timestamp Type: {type(event_ts)}") # <class 'datetime.datetime'>
print(f"Fetched Timestamp Value: {event_ts}")
print(f"Microseconds: {event_ts.microsecond}")
Advanced: Handling Time Zones with TIMESTAMP WITH TIME ZONE
If your Oracle column is TIMESTAMP WITH TIME ZONE, you'll get a Python datetime object with a tzinfo attribute.
# Create a table with a TIMESTAMP WITH TIME ZONE column
cursor.execute("""
CREATE TABLE my_timestamptz_table (
id NUMBER PRIMARY KEY,
event_tstz TIMESTAMP WITH TIME ZONE
)
""")
# You need a timezone-aware datetime object
from datetime import timezone, timedelta
tz_aware_datetime = datetime(2025, 10, 27, 15, 0, 0, tzinfo=timezone(timedelta(hours=5))) # UTC+5
# Tell the cursor to expect the correct type
cursor.setinputsizes(cx_Oracle.TIMESTAMP)
cursor.execute("INSERT INTO my_timestamptz_table (id, event_tstz) VALUES (:1, :2)", (301, tz_aware_datetime))
conn.commit()
print("Successfully inserted timezone-aware datetime.")
# --- Fetching it back ---
cursor.execute("SELECT id, event_tstz FROM my_timestamptz_table WHERE id = 301")
row = cursor.fetchone()
if row:
id_val, event_tstz = row
print(f"\nFetched TZ-Aware Timestamp: {event_tstz}")
print(f"Timezone Info: {event_tstz.tzinfo}")
Summary and Best Practices
| Task | Python Object | Oracle Type | Method |
|---|---|---|---|
Insert datetime into DATE |
datetime.datetime |
DATE |
Use cursor.setinputsizes(cx_Oracle.DATETIME) before cursor.execute(). |
Insert datetime into TIMESTAMP |
datetime.datetime |
TIMESTAMP |
Use cursor.setinputsizes(cx_Oracle.TIMESTAMP) before cursor.execute(). |
Insert datetime into TIMESTAMP WITH TZ |
datetime.datetime with tzinfo |
TIMESTAMP WITH TIME ZONE |
Use cursor.setinputsizes(cx_Oracle.TIMESTAMP). The object must be timezone-aware. |
| Fetch any Oracle Date/Timestamp | datetime.datetime |
DATE, TIMESTAMP, etc. |
It happens automatically. No special handling needed. |
Key Takeaways:
- For Inserts: Always use
cursor.setinputsizes()to explicitly define the Oracle data type (cx_Oracle.DATETIME,cx_Oracle.TIMESTAMP). This is the most reliable method. - For Fetches:
cx_Oracledoes the heavy lifting for you, converting Oracle dates into Pythondatetimeobjects. - Time Zones: Be mindful of time zones. If your Oracle column stores time zone information, ensure your Python
datetimeobject is timezone-aware (has atzinfoattribute). - Connection Handling: Always use
try...exceptblocks for database operations andconn.commit()to save changes. Useconn.rollback()in yourexceptblock to undo any partial transactions in case of an error.
