使用sqlx时SET TIME ZONE为何未按预期生效?
根据SQLx的PoolOptions文档示例,我编写了以下Rust代码:
use sqlx::Executor; use sqlx::postgres::PgPoolOptions; let pool = PgPoolOptions::new() .after_connect(|conn, _meta| Box::pin(async move { conn.execute("SET TIME ZONE 'Europe/Berlin';").await?; let result: (time::OffsetDateTime,) = sqlx::query_as("SELECT current_timestamp").fetch_one(conn).await?; println!("Current Time in Europe/Berlin: {}", result.0); Ok(()) })) .connect("postgres:// …").await?;
执行结果为:
Current Time in Europe/Berlin: 2024-02-06 22:39:37.194022 +00:00:00
这个结果不符合预期——当前柏林时间应该是23点,而不是UTC的22点。我的数据库默认时区为UTC(配置在postgresql.conf中),但直接在PostgreSQL里执行以下SQL能得到正确结果:
SET TIME ZONE 'Europe/Berlin'; SELECT current_timestamp;
请问问题出在哪里?
环境信息
- SQLx版本:
0.7.3 - SQLx启用特性:
"macros", "postgres", "runtime-tokio", "time" - 数据库版本:
Postgres v16.1 - 操作系统:
Windows 10 rustc --version:rustc 1.75.0 (82e1608df 2023-12-21)
问题根源
SQLx的PostgreSQL驱动使用二进制协议与数据库交互,对于TIMESTAMPTZ(带时区的时间戳)类型,会直接读取数据库存储的UTC原始值,完全忽略会话级别的SET TIME ZONE配置。
而你直接在PostgreSQL客户端(如psql)执行SQL时,用的是文本协议,数据库会根据当前会话时区将UTC时间转换为对应时区的时间后再格式化输出,所以能得到正确的柏林时间。
解决方案
你可以通过以下两种方式解决这个问题:
方案1:查询时明确转换时区
修改SQL查询语句,直接让数据库返回目标时区的本地时间:
SELECT current_timestamp AT TIME ZONE 'Europe/Berlin'
对应的Rust类型使用time::PrimitiveDateTime(不带时区的时间结构),此时打印结果即为柏林本地时间:
let result: (time::PrimitiveDateTime,) = sqlx::query_as("SELECT current_timestamp AT TIME ZONE 'Europe/Berlin'").fetch_one(conn).await?; println!("Current Time in Europe/Berlin: {}", result.0);
方案2:在客户端转换时区
保留原查询逻辑,获取UTC的OffsetDateTime后,手动转换为柏林时区。需要确保time crate启用iana-time-zone特性(在Cargo.toml中配置:time = { version = "...", features = ["iana-time-zone"] }):
use time::TimeZone; // ... 原连接代码 ... let result: (time::OffsetDateTime,) = sqlx::query_as("SELECT current_timestamp").fetch_one(conn).await?; let berlin_tz = time::IanaTimeZone::try_from("Europe/Berlin").unwrap(); let berlin_time = result.0.to_timezone(berlin_tz); println!("Current Time in Europe/Berlin: {}", berlin_time);
额外说明
如果需要所有数据库操作默认使用柏林时区,除了SET TIME ZONE,还可以在连接URL中指定时区参数:
.connect("postgres://user:pass@host/db?options=-c%20timezone%3DEurope%2FBerlin").await?;
但这仅影响数据库端函数(如now()、current_timestamp)的计算逻辑,SQLx通过二进制协议读取的仍然是UTC原始值,最终还是需要客户端转换才能得到正确的本地时间显示。
内容的提问来源于stack exchange,提问作者Fred Hors

