绑定QDateTime到MySQL预处理查询时UTC时间异常排查
我有一个包含datetime字段的MySQL数据库表,代码运行环境的系统时区为意大利时区(GMT+1)。当使用QSqlQuery预处理查询并绑定QDateTime变量时,插入操作成功,但查询返回的数据为UTC时间;而绑定QDateTime::toString(Qt::ISODate)生成的字符串时,插入和查询结果均正常。执行SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;查询,结果均为SYSTEM。请问这是驱动问题还是操作有误?
示例代码(Qt 6.8.1 + MySQL 8.0)
auto db = QSqlDatabase(QSqlDatabase::addDatabase("QMYSQL")); db.setHostName("127.0.0.1"); db.setDatabaseName("test_db"); db.open("test_user", "test_pwd"); QSqlQuery queryCreate("CREATE TABLE test_table (id int AUTO_INCREMENT, date_time datetime NOT NULL, PRIMARY KEY (id));", db); QSqlQuery queryInsertDt(db); queryInsertDt.prepare("INSERT INTO test_table (date_time) VALUE (:ts);"); auto now = QDateTime::currentDateTime(); queryInsertDt.bindValue(0, now); qDebug() << now << queryInsertDt.boundValue(":ts").toString() << queryInsertDt.boundValue(":ts").toDateTime(); // prints: QDateTime(2025-01-28 15:43:33.242 W. Europe Standard Time Qt::LocalTime) "2025-01-28T15:43:33.242" QDateTime(2025-01-28 15:43:33.242 W. Europe Standard Time Qt::LocalTime) queryInsertDt.exec(); QSqlQuery queryInsertStr(db); queryInsertStr.prepare("INSERT INTO test_table (date_time) VALUE (:ts);"); queryInsertStr.bindValue(0, now.toString(Qt::ISODate)); qDebug() << now << queryInsertStr.boundValue(":ts").toString() << queryInsertStr.boundValue(":ts").toDateTime(); // prints: QDateTime(2025-01-28 15:43:33.242 W. Europe Standard Time Qt::LocalTime) "2025-01-28T15:43:33" QDateTime(2025-01-28 15:43:33.000 W. Europe Standard Time Qt::LocalTime) queryInsertStr.exec(); QSqlQuery querySelect(db); querySelect.exec("SELECT id, date_time FROM test_table;"); int rowCount = 0; while(querySelect.next()) qDebug() << "Id" << querySelect.value(0).toInt() << "-" << querySelect.value(1).toString() << querySelect.value(1).toDateTime(); // Id 1 - "2025-01-28T14:43:33.000Z" QDateTime(2025-01-28 14:43:33.000 UTC Qt::UTC) // Id 2 - "2025-01-28T15:43:33.000Z" QDateTime(2025-01-28 15:43:33.000 UTC Qt::UTC) QSqlQuery querySelectWhere(db); querySelectWhere.exec(QString("SELECT id, date_time FROM test_table WHERE HOUR(date_time) = %1;").arg(now.time().hour())); while(querySelectWhere.next()) qDebug() << "Id" << querySelectWhere.value(0).toInt() << "-" << querySelectWhere.value(1).toString() << querySelectWhere.value(1).toDateTime(); // Id 2 - "2025-01-28T15:43:33.000Z" QDateTime(2025-01-28 15:43:33.000 UTC Qt::UTC) QSqlQuery queryDrop("DROP TABLE test_table;", db);
这不是操作错误,而是Qt的QMYSQL驱动处理QDateTime绑定的时区转换逻辑导致的:
绑定QDateTime变量时的行为
Qt的QMYSQL驱动在绑定QDateTime对象时,会默认将本地时间转换为UTC时间后发送给MySQL。即使MySQL的会话时区是SYSTEM(即意大利时区),驱动本身做了额外的时区转换,导致插入的时间是UTC时间(比本地时间晚1小时)。查询时,驱动又将MySQL返回的存储时间直接标记为UTC,所以显示结果为UTC时间。绑定字符串时的行为
当绑定QDateTime::toString(Qt::ISODate)生成的字符串时,驱动直接将字符串传递给MySQL,MySQL会按照会话时区(SYSTEM,意大利时区)解析这个时间字符串,存储为对应本地时间。查询时,返回的时间被驱动正确解析为对应时区的时间。
解决方法
方法一:显式设置MySQL会话时区
在打开数据库连接后,执行以下SQL语句,强制会话时区与本地时区一致,覆盖驱动的默认转换:SET time_zone = '+01:00';这样驱动在绑定
QDateTime时,会基于这个时区进行转换,确保插入和查询的时间正确。方法二:统一使用字符串绑定
继续使用QDateTime::toString(Qt::ISODate)生成字符串进行绑定,这种方式绕过驱动的时区转换逻辑,直接让MySQL按会话时区处理时间。方法三:使用带时区的字段类型
将表中的datetime字段改为datetime(6)或者timestamp类型,timestamp会自动存储UTC时间,但查询时会根据会话时区转换为本地时间,不过需要注意timestamp的时间范围限制(1970-01-01 00:00:01 UTC 到 2038-01-19 03:14:07 UTC)。
内容的提问来源于stack exchange,提问作者Lorenzo Aldrighetti

