Skip to content

Commit 1b32678

Browse files
committed
working datetimeoffset
1 parent 0d745b1 commit 1b32678

File tree

2 files changed

+95
-20
lines changed

2 files changed

+95
-20
lines changed

main.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,62 @@
22
from mssql_python import setup_logging
33
import os
44
import decimal
5+
# import pyodbc
56

67
setup_logging('stdout')
78

8-
conn_str = os.getenv("DB_CONNECTION_STRING")
9+
# conn_str = os.getenv("DB_CONNECTION_STRING")
10+
conn_str = "DRIVER={ODBC Driver 18 for SQL Server};Server=Saumya;DATABASE=master;UID=sa;PWD=HappyPass1234;Trust_Connection=yes;TrustServerCertificate=yes;"
11+
912
conn = connect(conn_str)
1013

11-
# conn.autocommit = True
14+
# # conn.autocommit = True
15+
16+
# cursor = conn.cursor()
17+
# cursor.execute("SELECT database_id, name from sys.databases;")
18+
# rows = cursor.fetchall()
19+
20+
# for row in rows:
21+
# print(f"Database ID: {row[0]}, Name: {row[1]}")
22+
23+
# cursor.close()
24+
# conn.close()
25+
26+
from datetime import datetime, timezone, timedelta
1227

28+
# Connect and get cursor
1329
cursor = conn.cursor()
14-
cursor.execute("SELECT database_id, name from sys.databases;")
15-
rows = cursor.fetchall()
1630

17-
for row in rows:
18-
print(f"Database ID: {row[0]}, Name: {row[1]}")
31+
# Create table (drop if exists)
32+
cursor.execute("""
33+
IF OBJECT_ID('dbo.test_datetimeoffset', 'U') IS NOT NULL
34+
DROP TABLE dbo.test_datetimeoffset;
35+
36+
CREATE TABLE dbo.test_datetimeoffset (
37+
id INT PRIMARY KEY,
38+
dt DATETIMEOFFSET
39+
)
40+
""")
41+
42+
# Insert a row
43+
dt_offset = datetime(2025, 9, 9, 15, 30, 45, 123456, tzinfo=timezone(timedelta(hours=5, minutes=30)))
44+
cursor.execute("INSERT INTO test_datetimeoffset (id, dt) VALUES (?, ?)", 1, dt_offset)
45+
conn.commit()
46+
print("Insertion done. Verify in SSMS.")
47+
48+
# --- Fetch the row ---
49+
cursor.execute("SELECT id, dt FROM dbo.test_datetimeoffset WHERE id = ?", 1)
50+
row = cursor.fetchone()
51+
52+
if row:
53+
fetched_id, fetched_dt = row
54+
print(f"Fetched ID: {fetched_id}")
55+
print(f"Fetched DATETIMEOFFSET: {fetched_dt} (tzinfo: {fetched_dt.tzinfo})")
1956

20-
cursor.close()
21-
conn.close()
57+
# Optional check
58+
if fetched_dt == dt_offset:
59+
print("✅ Fetch successful: Datetime matches inserted value")
60+
else:
61+
print("⚠️ Fetch mismatch: Datetime does not match inserted value")
62+
else:
63+
print("No row fetched.")

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -525,18 +525,51 @@ SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params,
525525
if (!py::isinstance(param, datetimeType)) {
526526
ThrowStdException(MakeParamMismatchErrorStr(paramInfo.paramCType, paramIndex));
527527
}
528-
SQL_TIMESTAMP_STRUCT* sqlTimestampPtr =
529-
AllocateParamBuffer<SQL_TIMESTAMP_STRUCT>(paramBuffers);
530-
sqlTimestampPtr->year = static_cast<SQLSMALLINT>(param.attr("year").cast<int>());
531-
sqlTimestampPtr->month = static_cast<SQLUSMALLINT>(param.attr("month").cast<int>());
532-
sqlTimestampPtr->day = static_cast<SQLUSMALLINT>(param.attr("day").cast<int>());
533-
sqlTimestampPtr->hour = static_cast<SQLUSMALLINT>(param.attr("hour").cast<int>());
534-
sqlTimestampPtr->minute = static_cast<SQLUSMALLINT>(param.attr("minute").cast<int>());
535-
sqlTimestampPtr->second = static_cast<SQLUSMALLINT>(param.attr("second").cast<int>());
536-
// SQL server supports in ns, but python datetime supports in µs
537-
sqlTimestampPtr->fraction = static_cast<SQLUINTEGER>(
538-
param.attr("microsecond").cast<int>() * 1000); // Convert µs to ns
539-
dataPtr = static_cast<void*>(sqlTimestampPtr);
528+
if (paramInfo.paramSQLType == SQL_TIMESTAMP) {
529+
// Handle naive datetime
530+
SQL_TIMESTAMP_STRUCT* tsPtr = AllocateParamBuffer<SQL_TIMESTAMP_STRUCT>(paramBuffers);
531+
tsPtr->year = static_cast<SQLSMALLINT>(param.attr("year").cast<int>());
532+
tsPtr->month = static_cast<SQLUSMALLINT>(param.attr("month").cast<int>());
533+
tsPtr->day = static_cast<SQLUSMALLINT>(param.attr("day").cast<int>());
534+
tsPtr->hour = static_cast<SQLUSMALLINT>(param.attr("hour").cast<int>());
535+
tsPtr->minute = static_cast<SQLUSMALLINT>(param.attr("minute").cast<int>());
536+
tsPtr->second = static_cast<SQLUSMALLINT>(param.attr("second").cast<int>());
537+
tsPtr->fraction = static_cast<SQLUINTEGER>(param.attr("microsecond").cast<int>() * 1000);
538+
dataPtr = static_cast<void*>(tsPtr);
539+
}
540+
else if (paramInfo.paramSQLType == SQL_SS_TIMESTAMPOFFSET) {
541+
// Handle tz-aware datetime → SQL_DATETIMEOFFSET
542+
SQL_SS_TIMESTAMPOFFSET_STRUCT* dtoPtr = AllocateParamBuffer<SQL_SS_TIMESTAMPOFFSET_STRUCT>(paramBuffers);
543+
int year = param.attr("year").cast<int>();
544+
if (year < 1753 || year > 9999) {
545+
ThrowStdException("Date out of range for SQL Server (1753-9999) at paramIndex " + std::to_string(paramIndex));
546+
}
547+
dtoPtr->year = static_cast<SQLSMALLINT>(year);
548+
dtoPtr->month = static_cast<SQLUSMALLINT>(param.attr("month").cast<int>());
549+
dtoPtr->day = static_cast<SQLUSMALLINT>(param.attr("day").cast<int>());
550+
dtoPtr->hour = static_cast<SQLUSMALLINT>(param.attr("hour").cast<int>());
551+
dtoPtr->minute = static_cast<SQLUSMALLINT>(param.attr("minute").cast<int>());
552+
dtoPtr->second = static_cast<SQLUSMALLINT>(param.attr("second").cast<int>());
553+
dtoPtr->fraction = static_cast<SQLUINTEGER>(param.attr("microsecond").cast<int>() * 1000);
554+
555+
py::object tzinfo = param.attr("tzinfo");
556+
if (tzinfo.is_none()) {
557+
ThrowStdException("Datetime object must have tzinfo for DATETIMEOFFSET at paramIndex " + std::to_string(paramIndex));
558+
}
559+
560+
py::object utcoffset = tzinfo.attr("utcoffset")(param);
561+
if (utcoffset.is_none()) {
562+
ThrowStdException("utcoffset is None for DATETIMEOFFSET at paramIndex " + std::to_string(paramIndex));
563+
}
564+
565+
int total_seconds = static_cast<int>(utcoffset.attr("total_seconds")().cast<double>());
566+
dtoPtr->timezone_hour = static_cast<SQLSMALLINT>(total_seconds / 3600);
567+
dtoPtr->timezone_minute = static_cast<SQLSMALLINT>((abs(total_seconds) % 3600) / 60);
568+
dataPtr = static_cast<void*>(dtoPtr);
569+
}
570+
else {
571+
ThrowStdException("Unsupported SQL type for timestamp at paramIndex " + std::to_string(paramIndex));
572+
}
540573
break;
541574
}
542575
case SQL_C_NUMERIC: {

0 commit comments

Comments
 (0)