概述
最近项目里面的按照时区来计算的时候存在少计算了8小时的问题;
timestamptz 是 PostgreSQL 中的一种时间/日期类型,完整名称是 timestamp with time zone。它和普通的 timestamp(即 timestamp without time zone)有一些区别:
timestamptz 带时区的时间戳(推荐)PostgreSQL 内部会存储为 UTC 时间;查询时会根据你的 会话时区(TimeZone 配置) 自动转换显示;
timestamp:不带时区;只存储一个绝对时间,不做时区转换;
原因分析
PostgreSQL 内部存储 UTC
• timestamptz 实际存储的是 UTC 时间
• 比如你在东八区插入 ‘2025-10-21 00:00:00’,数据库会存储为 ‘2025-10-20 16:00:00+00’
GORM / Go 的 time.Time 默认
• Go 的 time.Time 有 Location 信息
• 如果直接用 time.Date(2025,10,21,0,0,0,0,time.UTC),查出来会跟本地时区偏 8 小时
• 如果数据库/Go 会话时区不一致,会导致查询 WHERE submit_time >= ‘今天 0 点’ 时,结果偏移
解决方案 a
方法 A:在 Go 中使用本地时区创建时间
1 2 3 4 5 6 7
| loc, _ := time.LoadLocation("Asia/Shanghai") today := time.Now().In(loc) startOfDay := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, loc)
var rows []CrossChainTx db.Where("submit_time >= ?", startOfDay).Find(&rows)
|
封装起来的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func GetCSTDayRange(daysAgo int) (start, end time.Time) { loc, err := time.LoadLocation("Asia/Shanghai") if err != nil { loc = time.FixedZone("CST", 8*3600) }
now := time.Now().In(loc).AddDate(0, 0, -daysAgo)
start = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
end = start.Add(24 * time.Hour) return }
func GetCSTTodayRange() (start, end time.Time) { return GetCSTDayRange(0) }
|
解决方案 b
在 SQL 里使用 PostgreSQL 函数处理
• PostgreSQL 可以在查询里直接按东八区截断日期:
1 2 3
| SELECT * FROM cross_chain_tx WHERE submit_time >= date_trunc('day', now() AT TIME ZONE 'Asia/Shanghai');
|
• date_trunc('day', ...) 截断到当地 0 点
• AT TIME ZONE 'Asia/Shanghai' 保证是东八区
在 GORM 中:
1 2 3 4 5
| db.Raw(` SELECT * FROM cross_chain_tx WHERE submit_time >= date_trunc('day', now() AT TIME ZONE 'Asia/Shanghai') `).Scan(&rows)
|
方法 C:统一使用 UTC 处理
• 服务器和数据库都统一用 UTC
• 在 Go 中处理时间显示时,再转换到东八区
• 优点:逻辑简单,跨时区可靠
1 2 3
| utcNow := time.Now().UTC() startOfDayUTC := time.Date(utcNow.Year(), utcNow.Month(), utcNow.Day(), 0, 0, 0, 0, time.UTC) db.Where("submit_time >= ?", startOfDayUTC).Find(&rows)
|
• 但是显示给用户时,需要 .In(loc) 转成东八区
实务推荐:如果只是国内服务,使用 方法 A(Go 本地时区) 最方便。
如果有多时区用户,统一用 UTC + 显示转换更安全。
附带
c++ 版本
1 2
| auto tz = std::chrono::locate_zone("Asia/Shanghai"); auto now = std::chrono::zoned_time(tz, std::chrono::system_clock::now());
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <iostream> #include <ctime> #include <chrono>
std::time_t getTodayStartCST() { using namespace std::chrono;
system_clock::time_point now = system_clock::now();
std::time_t t_now = system_clock::to_time_t(now);
std::tm utc_tm = *std::gmtime(&t_now);
utc_tm.tm_hour = 0; utc_tm.tm_min = 0; utc_tm.tm_sec = 0; std::time_t utc_midnight = timegm(&utc_tm);
const int CST_OFFSET = 8 * 3600; std::time_t cst_midnight = utc_midnight - CST_OFFSET;
return cst_midnight; }
int main() { std::time_t start_cst = getTodayStartCST();
std::cout << "CST 0点 (UTC时间): " << std::asctime(std::gmtime(&start_cst));
std::time_t display = start_cst + 8 * 3600; std::cout << "CST 0点 (北京时间): " << std::asctime(std::gmtime(&display));
return 0; }
|
c 语言版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <stdio.h> #include <time.h>
time_t get_today_start_cst() { time_t now; struct tm *utc_tm; struct tm tm_copy; const int CST_OFFSET = 8 * 3600;
time(&now); utc_tm = gmtime(&now); tm_copy = *utc_tm;
tm_copy.tm_hour = 0; tm_copy.tm_min = 0; tm_copy.tm_sec = 0;
time_t utc_midnight = timegm(&tm_copy);
time_t cst_midnight = utc_midnight - CST_OFFSET;
return cst_midnight; }
int main() { time_t start_cst = get_today_start_cst();
printf("CST 当天 0 点 (UTC时间): %s", asctime(gmtime(&start_cst)));
time_t beijing_time = start_cst + 8 * 3600; printf("CST 当天 0 点 (北京时间): %s", asctime(gmtime(&beijing_time)));
return 0; }
|
rust 版本
Cargo.toml
1 2
| [dependencies] chrono = "0.4"
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| use chrono::{Local, TimeZone, Timelike, Datelike, FixedOffset, Duration};
fn get_today_cst_range() -> (chrono::DateTime<FixedOffset>, chrono::DateTime<FixedOffset>) { let cst = FixedOffset::east_opt(8 * 3600).unwrap();
let now_cst = Local::now().with_timezone(&cst);
let start_of_day = cst .with_ymd_and_hms(now_cst.year(), now_cst.month(), now_cst.day(), 0, 0, 0) .unwrap();
let end_of_day = start_of_day + Duration::hours(24);
(start_of_day, end_of_day) }
fn main() { let (start, end) = get_today_cst_range();
println!("🕛 东八区当天 0 点: {}", start); println!("🕕 东八区当天 24 点: {}", end); }
|