@@ -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// -------------------------------------------------------------------------------------------------
@@ -1945,6 +1947,7 @@ SQLRETURN BindParameterArray(SQLHANDLE hStmt,
19451947 break ;
19461948 }
19471949 case SQL_C_TYPE_TIMESTAMP: {
1950+ std::cout<<" Binding Timestamp param at index " <<paramIndex<<std::endl;
19481951 SQL_TIMESTAMP_STRUCT* tsArray = AllocateParamBufferArray<SQL_TIMESTAMP_STRUCT>(tempBuffers, paramSetSize);
19491952 strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
19501953 for (size_t i = 0 ; i < paramSetSize; ++i) {
@@ -1967,6 +1970,67 @@ SQLRETURN BindParameterArray(SQLHANDLE hStmt,
19671970 bufferLength = sizeof (SQL_TIMESTAMP_STRUCT);
19681971 break ;
19691972 }
1973+ case SQL_C_SS_TIMESTAMPOFFSET: {
1974+ std::cout<<" Binding DateTimeOffset param at index " <<paramIndex<<std::endl;
1975+ DateTimeOffset* dtoArray = AllocateParamBufferArray<DateTimeOffset>(tempBuffers, paramSetSize);
1976+ strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
1977+
1978+ py::object datetimeType = py::module_::import (" datetime" ).attr (" datetime" );
1979+
1980+ for (size_t i = 0 ; i < paramSetSize; ++i) {
1981+ const py::handle& param = columnValues[i];
1982+
1983+ if (param.is_none ()) {
1984+ std::memset (&dtoArray[i], 0 , sizeof (DateTimeOffset));
1985+ strLenOrIndArray[i] = SQL_NULL_DATA;
1986+ } else {
1987+ if (!py::isinstance (param, datetimeType)) {
1988+ ThrowStdException (MakeParamMismatchErrorStr (info.paramCType , paramIndex));
1989+ }
1990+
1991+ py::object tzinfo = param.attr (" tzinfo" );
1992+ if (tzinfo.is_none ()) {
1993+ ThrowStdException (" Datetime object must have tzinfo for SQL_C_SS_TIMESTAMPOFFSET at paramIndex " +
1994+ std::to_string (paramIndex));
1995+ }
1996+
1997+ // Convert the Python datetime object to UTC before binding.
1998+ // This is the crucial step to ensure timezone normalization.
1999+ py::object datetimeModule = py::module_::import (" datetime" );
2000+ py::object utc_dt = param.attr (" astimezone" )(datetimeModule.attr (" timezone" ).attr (" utc" ));
2001+ std::cout<<" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" <<std::endl;
2002+ // --- TEMPORARY DEBUGGING: LOG THE UTC VALUES ---
2003+ LOG (" Binding UTC values: {}-{}-{} {}:{}:{}.{} +00:00" ,
2004+ utc_dt.attr (" year" ).cast <int >(),
2005+ utc_dt.attr (" month" ).cast <int >(),
2006+ utc_dt.attr (" day" ).cast <int >(),
2007+ utc_dt.attr (" hour" ).cast <int >(),
2008+ utc_dt.attr (" minute" ).cast <int >(),
2009+ utc_dt.attr (" second" ).cast <int >(),
2010+ utc_dt.attr (" microsecond" ).cast <int >()
2011+ );
2012+
2013+ // Now, populate the C++ struct using the UTC-converted object.
2014+ dtoArray[i].year = static_cast <SQLSMALLINT>(utc_dt.attr (" year" ).cast <int >());
2015+ dtoArray[i].month = static_cast <SQLUSMALLINT>(utc_dt.attr (" month" ).cast <int >());
2016+ dtoArray[i].day = static_cast <SQLUSMALLINT>(utc_dt.attr (" day" ).cast <int >());
2017+ dtoArray[i].hour = static_cast <SQLUSMALLINT>(utc_dt.attr (" hour" ).cast <int >());
2018+ dtoArray[i].minute = static_cast <SQLUSMALLINT>(utc_dt.attr (" minute" ).cast <int >());
2019+ dtoArray[i].second = static_cast <SQLUSMALLINT>(utc_dt.attr (" second" ).cast <int >());
2020+ dtoArray[i].fraction = static_cast <SQLUINTEGER>(utc_dt.attr (" microsecond" ).cast <int >() * 1000 );
2021+
2022+ // Since we've converted to UTC, the timezone offset is always 0.
2023+ dtoArray[i].timezone_hour = 0 ;
2024+ dtoArray[i].timezone_minute = 0 ;
2025+
2026+ strLenOrIndArray[i] = sizeof (DateTimeOffset);
2027+ }
2028+ }
2029+
2030+ dataPtr = dtoArray;
2031+ bufferLength = sizeof (DateTimeOffset);
2032+ break ;
2033+ }
19702034 case SQL_C_NUMERIC: {
19712035 SQL_NUMERIC_STRUCT* numericArray = AllocateParamBufferArray<SQL_NUMERIC_STRUCT>(tempBuffers, paramSetSize);
19722036 strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
@@ -2642,6 +2706,7 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
26422706 microseconds,
26432707 tzinfo
26442708 );
2709+ py_dt = py_dt.attr (" astimezone" )(datetime.attr (" timezone" ).attr (" utc" ));
26452710 row.append (py_dt);
26462711 } else {
26472712 LOG (" Error fetching DATETIMEOFFSET for column {}, ret={}" , i, ret);
@@ -2912,6 +2977,13 @@ SQLRETURN SQLBindColums(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& column
29122977 ret = SQLBindCol_ptr (hStmt, col, SQL_C_BINARY, buffers.charBuffers [col - 1 ].data (),
29132978 columnSize, buffers.indicators [col - 1 ].data ());
29142979 break ;
2980+ case SQL_SS_TIMESTAMPOFFSET:
2981+ buffers.datetimeoffsetBuffers [col - 1 ].resize (fetchSize);
2982+ ret = SQLBindCol_ptr (hStmt, col, SQL_C_SS_TIMESTAMPOFFSET,
2983+ buffers.datetimeoffsetBuffers [col - 1 ].data (),
2984+ sizeof (DateTimeOffset) * fetchSize,
2985+ buffers.indicators [col - 1 ].data ());
2986+ break ;
29152987 default :
29162988 std::wstring columnName = columnMeta[" ColumnName" ].cast <std::wstring>();
29172989 std::ostringstream errorString;
@@ -3127,6 +3199,43 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
31273199 buffers.timeBuffers [col - 1 ][i].second ));
31283200 break ;
31293201 }
3202+ case SQL_SS_TIMESTAMPOFFSET: {
3203+ // i = current row index in outer loop
3204+ SQLULEN rowIdx = i;
3205+ const DateTimeOffset& dtoValue = buffers.datetimeoffsetBuffers [col - 1 ][rowIdx];
3206+ SQLLEN indicator = buffers.indicators [col - 1 ][rowIdx];
3207+
3208+ if (indicator != SQL_NULL_DATA) {
3209+ // Compute total minutes offset
3210+ int totalMinutes = dtoValue.timezone_hour * 60 + dtoValue.timezone_minute ;
3211+
3212+ // Import Python datetime module
3213+ py::object datetime = py::module_::import (" datetime" );
3214+
3215+ // Construct tzinfo object for the original offset
3216+ py::object tzinfo = datetime.attr (" timezone" )(
3217+ datetime.attr (" timedelta" )(py::arg (" minutes" ) = totalMinutes)
3218+ );
3219+
3220+ // Construct Python datetime object with tzinfo
3221+ py::object py_dt = datetime.attr (" datetime" )(
3222+ dtoValue.year ,
3223+ dtoValue.month ,
3224+ dtoValue.day ,
3225+ dtoValue.hour ,
3226+ dtoValue.minute ,
3227+ dtoValue.second ,
3228+ dtoValue.fraction / 1000 , // ns → µs
3229+ tzinfo
3230+ );
3231+ py_dt = py_dt.attr (" astimezone" )(datetime.attr (" timezone" ).attr (" utc" ));
3232+ // Append to row
3233+ row.append (py_dt);
3234+ } else {
3235+ row.append (py::none ());
3236+ }
3237+ break ;
3238+ }
31303239 case SQL_GUID: {
31313240 SQLGUID* guidValue = &buffers.guidBuffers [col - 1 ][i];
31323241 uint8_t reordered[16 ];
@@ -3246,6 +3355,9 @@ size_t calculateRowSize(py::list& columnNames, SQLUSMALLINT numCols) {
32463355 case SQL_LONGVARBINARY:
32473356 rowSize += columnSize;
32483357 break ;
3358+ case SQL_SS_TIMESTAMPOFFSET:
3359+ rowSize += sizeof (DateTimeOffset); // your custom struct for SQL_C_SS_TIMESTAMPOFFSET
3360+ break ;
32493361 default :
32503362 std::wstring columnName = columnMeta[" ColumnName" ].cast <std::wstring>();
32513363 std::ostringstream errorString;
0 commit comments