【通用时区时间工具类】
通用时区时间工具类
客户1:嗨,这有个bug,我查当天的数据只能查到8点之后的,8点之前的没有
操作1:发现数据库存的数据对不上 少了八个小时,于是让ai开始改后端改sql,让数据库存正确的时间,更新!
客户2:原本9:00的数据在展示页变成了17:00
操作2:又让ai埋头苦干改前端
也不知道ai在叽里咕噜说什么,但是出现次数最多的是“时区”导致的问题
UTC时区 (协调世界时)
UTC时区是全球统一的时间标准,不受时区和夏令时影响,是所有时区计算的基准,如北京时间为 UTC+8,纽约时间为 UTC-4/UTC-5 (夏令时),服务器和数据库应统一使用 UTC 存储时间,避免地域差异导致的混乱
-
时间示例:2026-03-31 12:00:00 UTC
-
ISO 8601 简写:Z(最常用!)
例:2026-03-31T12:00:00Z → 代表UTC 时间 -
固定偏移量时区
格式:
- UTC±HH
- UTC±HH:MM
- ±HH:MM(ISO 标准)
- ±HHMM
例如:
中国:UTC+8、+08:00、+0800
纽约冬令时:UTC-5、-05:00
纽约夏令时:UTC-4、-04:00
伦敦冬令时:UTC+0、±00:00ISO 时间示例:
2026-03-31T20:00:00+08:00
也就是说UTC表示的时间会比北京时间少8个小时,所以导致客户1反馈的问题,而我的操作1将数据库中存的UTC时间变成了北京时间(这其实是不对的,如果软件全球化会导致时间混乱,所以数据库中应该存UTC时区的时间)
数据库查询
mysql中可以通过CONVERT_TZ将UTC → 北京时间
例如
---sql1 ✅ 安全:函数作用在查询结果,索引有效SELECT
CONVERT_TZ(create_time, '+00:00', '+08:00') AS beijing_time
FROM table
WHERE create_time >= '2026-04-01 00:00:00'; -- 条件用UTC时间
--- sql2 ❌ 致命:函数作用在索引字段,索引失效 → 慢SQL
SELECT * FROM table
WHERE CONVERT_TZ(create_time, '+00:00', '+08:00') = '2026-04-01 17:00:00';
我们知道,当条件查询时,查询条件使用函数会导致索引失效,so? sql2 这段代码完全喊天菩萨!正确的做法应该在参数传入之前就把时间转好(这是工具类需要实现的功能点)
前端展示
在没有进行bug修复之前,由于错错得对,虽然查询时间有问题,但是界面展示是没有问题的,原因是React进行了时区转换,React 本身没有任何时区设置,页面显示时间完全由 浏览器的 JavaScript 引擎决定;
后端(Java)存 UTC 时间(比如 2026-03-31T12:00:00Z,末尾 Z = UTC)
传给前端的是 UTC 时间戳 / UTC 字符串
前端用 new Date(utc时间) 解析时,JS 会自动将 UTC 时间 → 转换为「用户设备的本地时区」时间
本地时区 = 用户设备(电脑 / 手机)操作系统设置的时区
关键配置
- JDBC 连接串配置中serverTimezone=UTC
- SpringBoot 配置中
spring:
timezone: UTC
jackson:
time-zone: UTC # JSON序列化统一UTC
工具类
综上,让ai埋头苦干写个工具类
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
/**
* 全局统一时间工具类
* 规范:
* 1. 数据库存储 = UTC Instant
* 2. 业务逻辑 = 北京时间(Asia/Shanghai)
* 3. 查询MySQL:一律用【北京时间转UTC】,WHERE条件绝不使用CONVERT_TZ
* 4. 全程不依赖JVM默认时区
*/
public final class DateTimeUtil {
// ===================== 1. 固定时区常量(全局唯一,不许改) =====================
public static final ZoneId ZONE_UTC = ZoneId.of("UTC");
public static final ZoneId ZONE_SHANGHAI = ZoneId.of("Asia/Shanghai");
// 标准格式化(不带时区,业务展示用)
public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter FORMATTER_DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private DateTimeUtil() {}
// ===================== 2. 获取当前时间(强制指定时区,禁止用原生XXX.now()) =====================
/**
* 当前UTC时间戳 → 存入数据库、MySQL、Redis
*/
public static Instant nowUtcInstant() {
return Instant.now();
}
/**
* 当前北京时间(带时区)
*/
public static ZonedDateTime nowShanghai() {
return ZonedDateTime.now(ZONE_SHANGHAI);
}
/**
* 当前北京时间(本地时间,无时区信息,仅业务展示)
*/
public static LocalDateTime nowShanghaiLocal() {
return LocalDateTime.now(ZONE_SHANGHAI);
}
// ===================== 3. 核心:北京时间 ↔ UTC 互转(最重要!) =====================
/**
* 北京 LocalDateTime → UTC Instant(用于存库、MySQL查询)
*/
public static Instant shanghaiToUtc(LocalDateTime shanghaiLocal) {
return shanghaiLocal.atZone(ZONE_SHANGHAI).toInstant();
}
/**
* UTC Instant → 北京 LocalDateTime(用于前端展示、业务逻辑)
*/
public static LocalDateTime utcToShanghai(Instant utcInstant) {
return LocalDateTime.ofInstant(utcInstant, ZONE_SHANGHAI);
}
// ===================== 4. 日期范围:北京某天 开始/结束 转UTC(解决慢SQL!) =====================
/**
* 北京日期(yyyy-MM-dd)→ 当天 00:00:00 的UTC时间
* 用于 WHERE create_time >= ?
*/
public static Instant shanghaiDayStart(String yyyyMMdd) {
LocalDate localDate = LocalDate.parse(yyyyMMdd, FORMATTER_DATE);
LocalDateTime start = LocalDateTime.of(localDate, LocalTime.MIN);
return shanghaiToUtc(start);
}
/**
* 北京日期(yyyy-MM-dd)→ 当天 23:59:59.999 的UTC时间
* 用于 WHERE create_time <= ?
*/
public static Instant shanghaiDayEnd(String yyyyMMdd) {
LocalDate localDate = LocalDate.parse(yyyyMMdd, FORMATTER_DATE);
LocalDateTime end = LocalDateTime.of(localDate, LocalTime.MAX);
return shanghaiToUtc(end);
}
// ===================== 5. 字符串解析/格式化(带时区,绝对安全) =====================
/**
* 解析北京时间字符串 → Instant(UTC)
*/
public static Instant parseShanghaiToUtc(String timeStr) {
LocalDateTime local = LocalDateTime.parse(timeStr, FORMATTER);
return shanghaiToUtc(local);
}
/**
* UTC时间 → 北京时间字符串
*/
public static String formatUtcToShanghai(Instant utcInstant) {
return utcToShanghai(utcInstant).format(FORMATTER);
}
// ===================== 6. 旧 Date 兼容 =====================
public static Instant dateToUtc(Date date) {
return date.toInstant();
}
public static Date utcToDate(Instant instant) {
return Date.from(instant);
}
}
- 存库 Instant now = DateTimeUtil.nowUtcInstant();
- 前端显示 LocalDateTime shanghaiTime = DateTimeUtil.utcToShanghai(utcInstant);(React 拿到后不会多 + 8、不会少 8)
- 查询全天
Instant start = DateTimeUtil.shanghaiDayStart(“2026-04-01”);
Instant end = DateTimeUtil.shanghaiDayEnd(“2026-04-01”);
| 时间类 | now () 方法默认时区 | 携带时区信息? | 核心用途 |
|---|---|---|---|
| Instant | UTC | 无 | 存储、计算、基准时间 |
| LocalDateTime/LocalDate/LocalTime | JVM 默认时区 | 无 | 纯本地展示(无时区) |
| ZonedDateTime | JVM 默认时区 | 无 | 完整时区处理、夏令时 |
数据库字段
- TIMESTAMP:MySQL 自动转 UTC 存储,推荐
- DATETIME:纯文本存储,需手动保证存 UTC
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)