Skip to content

Commit 306e41b

Browse files
Fixed bug when fetching a timestamp with nanosecond precision into a
data frame (#538).
1 parent 30d956e commit 306e41b

File tree

4 files changed

+52
-26
lines changed

4 files changed

+52
-26
lines changed

doc/src/release_notes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ oracledb `3.4.1 <https://github.com/oracle/python-oracledb/compare/v3.4.0...v3.4
1919
Thin Mode Changes
2020
+++++++++++++++++
2121

22+
#) Fixed bug when fetching a timestamp with nanosecond precision into a data
23+
frame
24+
(`issue 538 <https://github.com/oracle/python-oracledb/issues/538>`__).
2225
#) Fixed bug when adding a call to a PL/SQL function which returns LOBs to a
2326
:ref:`pipeline <pipelining>`.
2427
#) Fixed bug when using bind variables with scrollable cursors.

src/oracledb/impl/base/converters.pyx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,19 @@ cdef int convert_date_to_arrow_timestamp(ArrowArrayImpl array_impl,
186186
cdef:
187187
cydatetime.timedelta td
188188
cydatetime.datetime dt
189-
int64_t ts
189+
int64_t ts, us
190190
dt = convert_date_to_python(buffer)
191191
td = dt - EPOCH_DATE
192-
ts = int(cydatetime.total_seconds(td) * array_impl.schema_impl.time_factor)
192+
ts = (<int64_t> cydatetime.timedelta_days(td)) * (24 * 60 * 60) + \
193+
cydatetime.timedelta_seconds(td)
194+
ts *= array_impl.schema_impl.time_factor
195+
us = cydatetime.timedelta_microseconds(td)
196+
if array_impl.schema_impl.time_factor == 1_000:
197+
ts += us // 1_000
198+
elif array_impl.schema_impl.time_factor == 1_000_000:
199+
ts += us
200+
elif array_impl.schema_impl.time_factor != 1:
201+
ts += us * 1_000
193202
array_impl.append_int(ts)
194203

195204

tests/test_9300_dataframe_requested_schema.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import datetime
3030

31+
import oracledb
3132
import pyarrow
3233
import pytest
3334

@@ -281,28 +282,34 @@ def test_9310(dtype, value_is_date, conn):
281282

282283

283284
@pytest.mark.parametrize(
284-
"dtype,value_is_date",
285+
"dtype",
285286
[
286-
(pyarrow.date32(), True),
287-
(pyarrow.date64(), True),
288-
(pyarrow.timestamp("s"), False),
289-
(pyarrow.timestamp("us"), False),
290-
(pyarrow.timestamp("ms"), False),
291-
(pyarrow.timestamp("ns"), False),
287+
pyarrow.date32(),
288+
pyarrow.date64(),
289+
pyarrow.timestamp("s"),
290+
pyarrow.timestamp("us"),
291+
pyarrow.timestamp("ms"),
292+
pyarrow.timestamp("ns"),
292293
],
293294
)
294-
def test_9311(dtype, value_is_date, conn):
295+
def test_9311(dtype, conn):
295296
"9311 - fetch_df_all() for TIMESTAMP"
296297
requested_schema = pyarrow.schema([("TIMESTAMP_COL", dtype)])
297-
value = datetime.datetime(2025, 1, 15)
298-
statement = "select cast(:1 as timestamp) from dual"
298+
value = datetime.datetime(1974, 4, 4, 0, 57, 54, 15079)
299+
var = conn.cursor().var(oracledb.DB_TYPE_TIMESTAMP)
300+
var.setvalue(0, value)
301+
statement = "select :1 from dual"
299302
ora_df = conn.fetch_df_all(
300-
statement, [value], requested_schema=requested_schema
303+
statement, [var], requested_schema=requested_schema
301304
)
302305
tab = pyarrow.table(ora_df)
303306
assert tab.field("TIMESTAMP_COL").type == dtype
304-
if value_is_date:
307+
if not isinstance(dtype, pyarrow.TimestampType):
305308
value = value.date()
309+
elif dtype.unit == "s":
310+
value = value.replace(microsecond=0)
311+
elif dtype.unit == "ms":
312+
value = value.replace(microsecond=(value.microsecond // 1000) * 1000)
306313
assert tab["TIMESTAMP_COL"][0].as_py() == value
307314

308315

tests/test_9400_dataframe_requested_schema_async.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import datetime
3030

31+
import oracledb
3132
import pyarrow
3233
import pytest
3334

@@ -298,28 +299,34 @@ async def test_9410(dtype, value_is_date, async_conn):
298299

299300

300301
@pytest.mark.parametrize(
301-
"dtype,value_is_date",
302+
"dtype",
302303
[
303-
(pyarrow.date32(), True),
304-
(pyarrow.date64(), True),
305-
(pyarrow.timestamp("s"), False),
306-
(pyarrow.timestamp("us"), False),
307-
(pyarrow.timestamp("ms"), False),
308-
(pyarrow.timestamp("ns"), False),
304+
pyarrow.date32(),
305+
pyarrow.date64(),
306+
pyarrow.timestamp("s"),
307+
pyarrow.timestamp("us"),
308+
pyarrow.timestamp("ms"),
309+
pyarrow.timestamp("ns"),
309310
],
310311
)
311-
async def test_9411(dtype, value_is_date, async_conn):
312+
async def test_9411(dtype, async_conn):
312313
"9411 - fetch_df_all() for TIMESTAMP"
313314
requested_schema = pyarrow.schema([("TIMESTAMP_COL", dtype)])
314-
value = datetime.datetime(2025, 1, 15)
315-
statement = "select cast(:1 as timestamp) from dual"
315+
value = datetime.datetime(1974, 4, 4, 0, 57, 54, 15079)
316+
var = async_conn.cursor().var(oracledb.DB_TYPE_TIMESTAMP)
317+
var.setvalue(0, value)
318+
statement = "select :1 from dual"
316319
ora_df = await async_conn.fetch_df_all(
317-
statement, [value], requested_schema=requested_schema
320+
statement, [var], requested_schema=requested_schema
318321
)
319322
tab = pyarrow.table(ora_df)
320323
assert tab.field("TIMESTAMP_COL").type == dtype
321-
if value_is_date:
324+
if not isinstance(dtype, pyarrow.TimestampType):
322325
value = value.date()
326+
elif dtype.unit == "s":
327+
value = value.replace(microsecond=0)
328+
elif dtype.unit == "ms":
329+
value = value.replace(microsecond=(value.microsecond // 1000) * 1000)
323330
assert tab["TIMESTAMP_COL"][0].as_py() == value
324331

325332

0 commit comments

Comments
 (0)