@@ -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