@@ -64,6 +64,20 @@ struct NumericData {
6464 : precision(precision), scale(scale), sign(sign), val(value) {}
6565};
6666
67+ // Struct to hold the DateTimeOffset structure
68+ struct DateTimeOffset
69+ {
70+ SQLSMALLINT year;
71+ SQLUSMALLINT month;
72+ SQLUSMALLINT day;
73+ SQLUSMALLINT hour;
74+ SQLUSMALLINT minute;
75+ SQLUSMALLINT second;
76+ SQLUINTEGER fraction; // Nanoseconds
77+ SQLSMALLINT timezone_hour; // Offset hours from UTC
78+ SQLSMALLINT timezone_minute; // Offset minutes from UTC
79+ };
80+
6781// Struct to hold data buffers and indicators for each column
6882struct ColumnBuffers {
6983 std::vector<std::vector<SQLCHAR>> charBuffers;
@@ -78,6 +92,7 @@ struct ColumnBuffers {
7892 std::vector<std::vector<SQL_TIME_STRUCT>> timeBuffers;
7993 std::vector<std::vector<SQLGUID>> guidBuffers;
8094 std::vector<std::vector<SQLLEN>> indicators;
95+ std::vector<std::vector<DateTimeOffset>> datetimeoffsetBuffers;
8196
8297 ColumnBuffers (SQLSMALLINT numCols, int fetchSize)
8398 : charBuffers(numCols),
@@ -91,23 +106,10 @@ struct ColumnBuffers {
91106 dateBuffers(numCols),
92107 timeBuffers(numCols),
93108 guidBuffers(numCols),
109+ datetimeoffsetBuffers(numCols),
94110 indicators(numCols, std::vector<SQLLEN>(fetchSize)) {}
95111};
96112
97- // Struct to hold the DateTimeOffset structure
98- struct DateTimeOffset
99- {
100- SQLSMALLINT year;
101- SQLUSMALLINT month;
102- SQLUSMALLINT day;
103- SQLUSMALLINT hour;
104- SQLUSMALLINT minute;
105- SQLUSMALLINT second;
106- SQLUINTEGER fraction; // Nanoseconds
107- SQLSMALLINT timezone_hour; // Offset hours from UTC
108- SQLSMALLINT timezone_minute; // Offset minutes from UTC
109- };
110-
111113// -------------------------------------------------------------------------------------------------
112114// Function pointer initialization
113115// -------------------------------------------------------------------------------------------------
@@ -1876,6 +1878,7 @@ SQLRETURN BindParameterArray(SQLHANDLE hStmt,
18761878 break ;
18771879 }
18781880 case SQL_C_TYPE_TIMESTAMP: {
1881+ std::cout<<" Binding Timestamp param at index " <<paramIndex<<std::endl;
18791882 SQL_TIMESTAMP_STRUCT* tsArray = AllocateParamBufferArray<SQL_TIMESTAMP_STRUCT>(tempBuffers, paramSetSize);
18801883 strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
18811884 for (size_t i = 0 ; i < paramSetSize; ++i) {
@@ -1898,6 +1901,67 @@ SQLRETURN BindParameterArray(SQLHANDLE hStmt,
18981901 bufferLength = sizeof (SQL_TIMESTAMP_STRUCT);
18991902 break ;
19001903 }
1904+ case SQL_C_SS_TIMESTAMPOFFSET: {
1905+ std::cout<<" Binding DateTimeOffset param at index " <<paramIndex<<std::endl;
1906+ DateTimeOffset* dtoArray = AllocateParamBufferArray<DateTimeOffset>(tempBuffers, paramSetSize);
1907+ strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
1908+
1909+ py::object datetimeType = py::module_::import (" datetime" ).attr (" datetime" );
1910+
1911+ for (size_t i = 0 ; i < paramSetSize; ++i) {
1912+ const py::handle& param = columnValues[i];
1913+
1914+ if (param.is_none ()) {
1915+ std::memset (&dtoArray[i], 0 , sizeof (DateTimeOffset));
1916+ strLenOrIndArray[i] = SQL_NULL_DATA;
1917+ } else {
1918+ if (!py::isinstance (param, datetimeType)) {
1919+ ThrowStdException (MakeParamMismatchErrorStr (info.paramCType , paramIndex));
1920+ }
1921+
1922+ py::object tzinfo = param.attr (" tzinfo" );
1923+ if (tzinfo.is_none ()) {
1924+ ThrowStdException (" Datetime object must have tzinfo for SQL_C_SS_TIMESTAMPOFFSET at paramIndex " +
1925+ std::to_string (paramIndex));
1926+ }
1927+
1928+ // Convert the Python datetime object to UTC before binding.
1929+ // This is the crucial step to ensure timezone normalization.
1930+ py::object datetimeModule = py::module_::import (" datetime" );
1931+ py::object utc_dt = param.attr (" astimezone" )(datetimeModule.attr (" timezone" ).attr (" utc" ));
1932+ std::cout<<" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" <<std::endl;
1933+ // --- TEMPORARY DEBUGGING: LOG THE UTC VALUES ---
1934+ LOG (" Binding UTC values: {}-{}-{} {}:{}:{}.{} +00:00" ,
1935+ utc_dt.attr (" year" ).cast <int >(),
1936+ utc_dt.attr (" month" ).cast <int >(),
1937+ utc_dt.attr (" day" ).cast <int >(),
1938+ utc_dt.attr (" hour" ).cast <int >(),
1939+ utc_dt.attr (" minute" ).cast <int >(),
1940+ utc_dt.attr (" second" ).cast <int >(),
1941+ utc_dt.attr (" microsecond" ).cast <int >()
1942+ );
1943+
1944+ // Now, populate the C++ struct using the UTC-converted object.
1945+ dtoArray[i].year = static_cast <SQLSMALLINT>(utc_dt.attr (" year" ).cast <int >());
1946+ dtoArray[i].month = static_cast <SQLUSMALLINT>(utc_dt.attr (" month" ).cast <int >());
1947+ dtoArray[i].day = static_cast <SQLUSMALLINT>(utc_dt.attr (" day" ).cast <int >());
1948+ dtoArray[i].hour = static_cast <SQLUSMALLINT>(utc_dt.attr (" hour" ).cast <int >());
1949+ dtoArray[i].minute = static_cast <SQLUSMALLINT>(utc_dt.attr (" minute" ).cast <int >());
1950+ dtoArray[i].second = static_cast <SQLUSMALLINT>(utc_dt.attr (" second" ).cast <int >());
1951+ dtoArray[i].fraction = static_cast <SQLUINTEGER>(utc_dt.attr (" microsecond" ).cast <int >() * 1000 );
1952+
1953+ // Since we've converted to UTC, the timezone offset is always 0.
1954+ dtoArray[i].timezone_hour = 0 ;
1955+ dtoArray[i].timezone_minute = 0 ;
1956+
1957+ strLenOrIndArray[i] = sizeof (DateTimeOffset);
1958+ }
1959+ }
1960+
1961+ dataPtr = dtoArray;
1962+ bufferLength = sizeof (DateTimeOffset);
1963+ break ;
1964+ }
19011965 case SQL_C_NUMERIC: {
19021966 SQL_NUMERIC_STRUCT* numericArray = AllocateParamBufferArray<SQL_NUMERIC_STRUCT>(tempBuffers, paramSetSize);
19031967 strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
@@ -2573,6 +2637,7 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
25732637 microseconds,
25742638 tzinfo
25752639 );
2640+ py_dt = py_dt.attr (" astimezone" )(datetime.attr (" timezone" ).attr (" utc" ));
25762641 row.append (py_dt);
25772642 } else {
25782643 LOG (" Error fetching DATETIMEOFFSET for column {}, ret={}" , i, ret);
@@ -2836,6 +2901,13 @@ SQLRETURN SQLBindColums(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& column
28362901 ret = SQLBindCol_ptr (hStmt, col, SQL_C_BINARY, buffers.charBuffers [col - 1 ].data (),
28372902 columnSize, buffers.indicators [col - 1 ].data ());
28382903 break ;
2904+ case SQL_SS_TIMESTAMPOFFSET:
2905+ buffers.datetimeoffsetBuffers [col - 1 ].resize (fetchSize);
2906+ ret = SQLBindCol_ptr (hStmt, col, SQL_C_SS_TIMESTAMPOFFSET,
2907+ buffers.datetimeoffsetBuffers [col - 1 ].data (),
2908+ sizeof (DateTimeOffset) * fetchSize,
2909+ buffers.indicators [col - 1 ].data ());
2910+ break ;
28392911 default :
28402912 std::wstring columnName = columnMeta[" ColumnName" ].cast <std::wstring>();
28412913 std::ostringstream errorString;
@@ -3051,6 +3123,43 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
30513123 buffers.timeBuffers [col - 1 ][i].second ));
30523124 break ;
30533125 }
3126+ case SQL_SS_TIMESTAMPOFFSET: {
3127+ // i = current row index in outer loop
3128+ SQLULEN rowIdx = i;
3129+ const DateTimeOffset& dtoValue = buffers.datetimeoffsetBuffers [col - 1 ][rowIdx];
3130+ SQLLEN indicator = buffers.indicators [col - 1 ][rowIdx];
3131+
3132+ if (indicator != SQL_NULL_DATA) {
3133+ // Compute total minutes offset
3134+ int totalMinutes = dtoValue.timezone_hour * 60 + dtoValue.timezone_minute ;
3135+
3136+ // Import Python datetime module
3137+ py::object datetime = py::module_::import (" datetime" );
3138+
3139+ // Construct tzinfo object for the original offset
3140+ py::object tzinfo = datetime.attr (" timezone" )(
3141+ datetime.attr (" timedelta" )(py::arg (" minutes" ) = totalMinutes)
3142+ );
3143+
3144+ // Construct Python datetime object with tzinfo
3145+ py::object py_dt = datetime.attr (" datetime" )(
3146+ dtoValue.year ,
3147+ dtoValue.month ,
3148+ dtoValue.day ,
3149+ dtoValue.hour ,
3150+ dtoValue.minute ,
3151+ dtoValue.second ,
3152+ dtoValue.fraction / 1000 , // ns → µs
3153+ tzinfo
3154+ );
3155+ py_dt = py_dt.attr (" astimezone" )(datetime.attr (" timezone" ).attr (" utc" ));
3156+ // Append to row
3157+ row.append (py_dt);
3158+ } else {
3159+ row.append (py::none ());
3160+ }
3161+ break ;
3162+ }
30543163 case SQL_GUID: {
30553164 row.append (
30563165 py::bytes (reinterpret_cast <const char *>(&buffers.guidBuffers [col - 1 ][i]),
@@ -3156,6 +3265,9 @@ size_t calculateRowSize(py::list& columnNames, SQLUSMALLINT numCols) {
31563265 case SQL_LONGVARBINARY:
31573266 rowSize += columnSize;
31583267 break ;
3268+ case SQL_SS_TIMESTAMPOFFSET:
3269+ rowSize += sizeof (DateTimeOffset); // your custom struct for SQL_C_SS_TIMESTAMPOFFSET
3270+ break ;
31593271 default :
31603272 std::wstring columnName = columnMeta[" ColumnName" ].cast <std::wstring>();
31613273 std::ostringstream errorString;
0 commit comments