@@ -525,51 +525,51 @@ SQLRETURN BindParameters(SQLHANDLE hStmt, const py::list& params,
525525 if (!py::isinstance (param, datetimeType)) {
526526 ThrowStdException (MakeParamMismatchErrorStr (paramInfo.paramCType , paramIndex));
527527 }
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- }
528+ // Checking if the object has a timezone
529+ py::object tzinfo = param.attr (" tzinfo" );
530+ if (tzinfo.is_none ()) {
531+ ThrowStdException (" Datetime object must have tzinfo for SQL_C_SS_TIMESTAMPOFFSET at paramIndex " + std::to_string (paramIndex));
532+ }
559533
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- }
534+ DateTimeOffset* dtoPtr = AllocateParamBuffer<DateTimeOffset>(paramBuffers);
564535
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));
536+ dtoPtr->year = static_cast <SQLSMALLINT>(param.attr (" year" ).cast <int >());
537+ dtoPtr->month = static_cast <SQLUSMALLINT>(param.attr (" month" ).cast <int >());
538+ dtoPtr->day = static_cast <SQLUSMALLINT>(param.attr (" day" ).cast <int >());
539+ dtoPtr->hour = static_cast <SQLUSMALLINT>(param.attr (" hour" ).cast <int >());
540+ dtoPtr->minute = static_cast <SQLUSMALLINT>(param.attr (" minute" ).cast <int >());
541+ dtoPtr->second = static_cast <SQLUSMALLINT>(param.attr (" second" ).cast <int >());
542+ dtoPtr->fraction = static_cast <SQLUINTEGER>(param.attr (" microsecond" ).cast <int >() * 1000 );
543+
544+ py::object utcoffset = tzinfo.attr (" utcoffset" )(param);
545+ int total_seconds = static_cast <int >(utcoffset.attr (" total_seconds" )().cast <double >());
546+ std::div_t div_result = std::div (total_seconds, 3600 );
547+ dtoPtr->timezone_hour = static_cast <SQLSMALLINT>(div_result.quot );
548+ dtoPtr->timezone_minute = static_cast <SQLSMALLINT>(div (div_result.rem , 60 ).quot );
549+
550+ dataPtr = static_cast <void *>(dtoPtr);
551+ bufferLength = sizeof (DateTimeOffset);
552+ strLenOrIndPtr = AllocateParamBuffer<SQLLEN>(paramBuffers);
553+ *strLenOrIndPtr = bufferLength;
554+ break ;
555+ }
556+ case SQL_C_TYPE_TIMESTAMP: {
557+ py::object datetimeType = py::module_::import (" datetime" ).attr (" datetime" );
558+ if (!py::isinstance (param, datetimeType)) {
559+ ThrowStdException (MakeParamMismatchErrorStr (paramInfo.paramCType , paramIndex));
572560 }
561+ SQL_TIMESTAMP_STRUCT* sqlTimestampPtr =
562+ AllocateParamBuffer<SQL_TIMESTAMP_STRUCT>(paramBuffers);
563+ sqlTimestampPtr->year = static_cast <SQLSMALLINT>(param.attr (" year" ).cast <int >());
564+ sqlTimestampPtr->month = static_cast <SQLUSMALLINT>(param.attr (" month" ).cast <int >());
565+ sqlTimestampPtr->day = static_cast <SQLUSMALLINT>(param.attr (" day" ).cast <int >());
566+ sqlTimestampPtr->hour = static_cast <SQLUSMALLINT>(param.attr (" hour" ).cast <int >());
567+ sqlTimestampPtr->minute = static_cast <SQLUSMALLINT>(param.attr (" minute" ).cast <int >());
568+ sqlTimestampPtr->second = static_cast <SQLUSMALLINT>(param.attr (" second" ).cast <int >());
569+ // SQL server supports in ns, but python datetime supports in µs
570+ sqlTimestampPtr->fraction = static_cast <SQLUINTEGER>(
571+ param.attr (" microsecond" ).cast <int >() * 1000 ); // Convert µs to ns
572+ dataPtr = static_cast <void *>(sqlTimestampPtr);
573573 break ;
574574 }
575575 case SQL_C_NUMERIC: {
0 commit comments