Skip to content

Commit b5a889f

Browse files
committed
Merged PR 5329: Enhance datetime parsing and timestamp handling for better compatibility with...
Enhance datetime parsing and timestamp handling to support fractional second. Return Bit in Bool format ---- #### AI description (iteration 1) #### PR Classification Enhancement for datetime parsing and timestamp handling. #### PR Summary This pull request enhances datetime parsing and timestamp handling for better compatibility, addressing the linked work item to convert Bit to Bool and support fractional seconds for DateTime. - `mssql_python/cursor.py`: Added support for parsing datetime strings with fractional seconds and ensured proper handling of fractional seconds. - `mssql_python/pybind/ddbc_bindings.cpp`: Converted microseconds to nanoseconds for SQL server compatibility and vice versa, and converted SQL_BIT to boolean. <!-- GitOpsUserAgent=GitOps.Apps.Server.pullrequestcopilot --> Related work items: #33924
1 parent 03343d2 commit b5a889f

File tree

3 files changed

+27
-14
lines changed

3 files changed

+27
-14
lines changed

mssql_python/cursor.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,28 @@ def _parse_datetime(self, param):
104104
A datetime.datetime object if parsing is successful, else None.
105105
"""
106106
formats = [
107+
"%Y-%m-%dT%H:%M:%S.%f", # ISO 8601 datetime with fractional seconds
107108
"%Y-%m-%dT%H:%M:%S", # ISO 8601 datetime
108109
"%Y-%m-%d %H:%M:%S.%f", # Datetime with fractional seconds (up to 3 digits)
110+
"%Y-%m-%d %H:%M:%S", # Datetime without fractional seconds
109111
]
110112
for fmt in formats:
111113
try:
112114
dt = datetime.datetime.strptime(param, fmt)
113-
if fmt == "%Y-%m-%d %H:%M:%S.%f" and len(param.split('.')[-1]) <= 3 or fmt == "%Y-%m-%dT%H:%M:%S":
114-
return dt
115+
116+
# If there are fractional seconds, ensure they do not exceed 7 digits
117+
if fmt in ["%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S.%f"]:
118+
fractional_part = param.split('.')[-1]
119+
# If the fractional part is more than 3 digits, truncate to 3 digits
120+
if len(fractional_part) > 3:
121+
fractional_part = fractional_part[:3]
122+
# Convert to microseconds
123+
dt = dt.replace(microsecond=int(fractional_part.ljust(3, "0")) * 1000)
124+
return dt # Valid datetime
115125
except ValueError:
116-
continue
117-
return None
126+
continue # Try next format
127+
128+
return None # If all formats fail, return None
118129

119130
def _parse_time(self, param):
120131
"""

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,9 @@ SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params,
518518
sqlTimestampPtr->hour = param.attr("hour").cast<int>();
519519
sqlTimestampPtr->minute = param.attr("minute").cast<int>();
520520
sqlTimestampPtr->second = param.attr("second").cast<int>();
521-
// TODO: timestamp.fraction field seems to involve some computation.
522-
// Handle this in python and pass result to pybind module?
523-
sqlTimestampPtr->fraction = 0;
521+
// SQL server supports in ns, but python datetime supports in µs
522+
sqlTimestampPtr->fraction = static_cast<SQLUINTEGER>(
523+
param.attr("microsecond").cast<int>() * 1000); // Convert µs to ns
524524
dataPtr = static_cast<void*>(sqlTimestampPtr);
525525
break;
526526
}
@@ -1058,7 +1058,8 @@ SQLRETURN SQLGetData_wrap(intptr_t StatementHandle, SQLUSMALLINT colCount, py::l
10581058
timestampValue.day,
10591059
timestampValue.hour,
10601060
timestampValue.minute,
1061-
timestampValue.second
1061+
timestampValue.second,
1062+
timestampValue.fraction / 1000 // Convert back ns to µs
10621063
)
10631064
);
10641065
} else {
@@ -1356,7 +1357,7 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
13561357
row.append(buffers.charBuffers[col - 1][i]);
13571358
break;
13581359
case SQL_BIT:
1359-
row.append(buffers.charBuffers[col - 1][i]);
1360+
row.append(static_cast<bool>(buffers.charBuffers[col - 1][i]));
13601361
break;
13611362
case SQL_REAL:
13621363
row.append(buffers.realBuffers[col - 1][i]);
@@ -1389,7 +1390,8 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
13891390
buffers.timestampBuffers[col - 1][i].day,
13901391
buffers.timestampBuffers[col - 1][i].hour,
13911392
buffers.timestampBuffers[col - 1][i].minute,
1392-
buffers.timestampBuffers[col - 1][i].second
1393+
buffers.timestampBuffers[col - 1][i].second,
1394+
buffers.timestampBuffers[col - 1][i].fraction / 1000 // Convert back ns to µs
13931395
)
13941396
);
13951397
break;

tests/test_004_cursor.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
1.23456789,
4242
"nvarchar data",
4343
time(12, 34, 56),
44-
datetime(2024, 5, 20, 12, 34, 56),
44+
datetime(2024, 5, 20, 12, 34, 56, 123000),
4545
date(2024, 5, 20),
4646
1.23456789
4747
)
@@ -51,7 +51,7 @@
5151
TEST_DATA,
5252
(2, 0, 0, 0, 0, 0, 0.0, "test1", time(0, 0, 0), datetime(2024, 1, 1, 0, 0, 0), date(2024, 1, 1), 0.0),
5353
(3, 1, 1, 1, 1, 1, 1.1, "test2", time(1, 1, 1), datetime(2024, 2, 2, 1, 1, 1), date(2024, 2, 2), 1.1),
54-
(4, 0, 127, 32767, 9223372036854775807, 2147483647, 1.23456789, "test3", time(12, 34, 56), datetime(2024, 5, 20, 12, 34, 56), date(2024, 5, 20), 1.23456789)
54+
(4, 0, 127, 32767, 9223372036854775807, 2147483647, 1.23456789, "test3", time(12, 34, 56), datetime(2024, 5, 20, 12, 34, 56, 123000), date(2024, 5, 20), 1.23456789)
5555
]
5656

5757
def drop_table_if_exists(cursor, table_name):
@@ -137,11 +137,11 @@ def test_insert_datetime_column(cursor, db_connection):
137137
drop_table_if_exists(cursor, "single_column")
138138
cursor.execute("CREATE TABLE single_column (datetime_column DATETIME)")
139139
db_connection.commit()
140-
cursor.execute("INSERT INTO single_column (datetime_column) VALUES (?)", [datetime(2024, 5, 20, 12, 34, 56)])
140+
cursor.execute("INSERT INTO single_column (datetime_column) VALUES (?)", [datetime(2024, 5, 20, 12, 34, 56, 123000)])
141141
db_connection.commit()
142142
cursor.execute("SELECT datetime_column FROM single_column")
143143
row = cursor.fetchone()
144-
assert row[0] == datetime(2024, 5, 20, 12, 34, 56), "Datetime column insertion/fetch failed"
144+
assert row[0] == datetime(2024, 5, 20, 12, 34, 56, 123000), "Datetime column insertion/fetch failed"
145145
except Exception as e:
146146
pytest.fail(f"Datetime column insertion/fetch failed: {e}")
147147
finally:

0 commit comments

Comments
 (0)