当设计或修改 REST API 响应结构、处理 API 返回值时触发。防止 API 设计缺陷导致的字段错位、类型歧义等问题。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
当代码出现 LocalDate.now() / LocalDateTime.now() / Instant.now() / new Date() / Date.now() / time.Now() 等"当前时间"调用,或涉及周/月/日起点、跨时区比较、数据库时间字段时触发。防止生产环境时区漂移导致的统计错乱。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
服务端代码里,"当前时间"几乎从不应该裸调。一个 docker 镜像默认 UTC、一个生产机配置 Asia/Shanghai,同一份代码跑出来的"本周起点"就差 8 小时。
配套:
query-performance-safety(性能) /multi-tenant-safety(隔离)
.now() 依赖 JVM 默认时区场景: LocalDate.now() / LocalDateTime.now() / Instant.now() / new Date() 不传时区
LocalDate.now() 在 UTC 服务器上返回的"今天"比业务时区少 1 天(凌晨场景)LocalDateTime.now() 拿到的是 JVM 默认时区时间,docker base image 经常默认 UTC// ❌ 错误:依赖 JVM 默认时区
LocalDateTime weekStart = LocalDate.now()
.with(DayOfWeek.MONDAY)
.atStartOfDay();
// ❌ 错误:跨日切换的统计依赖默认时区
LocalDate today = LocalDate.now();
List<Order> todayOrders = orderRepository.findByCreatedAtAfter(today.atStartOfDay());
// ❌ 错误:Date 用默认时区
Date now = new Date();
项目级业务时区常量 + 所有 .now() 显式传入:
// ✅ 项目常量类(如 TimeConstants.java)
public final class TimeConstants {
/** 系统业务时区(中国大陆) */
public static final ZoneId BIZ_ZONE = ZoneId.of("Asia/Shanghai");
private TimeConstants() {}
}
// ✅ 业务代码统一传入
LocalDateTime weekStart = LocalDate.now(BIZ_ZONE)
.with(DayOfWeek.MONDAY)
.atStartOfDay();
LocalDate today = LocalDate.now(BIZ_ZONE);
LocalDateTime nowBiz = LocalDateTime.now(BIZ_ZONE);
Instant instantNow = Instant.now(); // Instant 本身是 UTC 时间戳,无歧义
# 任何裸 .now() 在业务代码中都该 review
grep -rnE "LocalDate\.now\(\)|LocalDateTime\.now\(\)|LocalTime\.now\(\)" src/main/java/ \
| grep -v "TimeConstants\|@Configuration"
# Date 系列在新代码中应淘汰
grep -rnE "new Date\(\)|new GregorianCalendar\(\)" src/main/java/
# Go
grep -rnE "time\.Now\(\)" --include="*.go" .
# JS / TS
grep -rnE "new Date\(\)|Date\.now\(\)" --include="*.ts" --include="*.js" src/
BIZ_ZONE 常量.now(),必须显式时区Instant.now() 可以裸用(UTC 时间戳无歧义)Clock.fixed(instant, zone) 注入,不要硬编码 now()场景: 计算"本周起点 / 本月初 / 今日 00:00"用于统计聚合
跨日/跨周/跨月切换时间点最敏感。如果起点用 UTC 算,业务时区下"本周一 0 点"对应 UTC 周日 16:00,导致统计错位。
// ❌ 错误:周起点未指定时区
LocalDateTime weekStart = LocalDate.now()
.with(DayOfWeek.MONDAY)
.atStartOfDay();
// ❌ 错误:月起点未指定时区
LocalDateTime monthStart = LocalDate.now().withDayOfMonth(1).atStartOfDay();
// ✅ 周起点(业务周一 00:00)
LocalDateTime weekStart = LocalDate.now(BIZ_ZONE)
.with(DayOfWeek.MONDAY)
.atStartOfDay();
// ✅ 月起点
LocalDateTime monthStart = LocalDate.now(BIZ_ZONE).withDayOfMonth(1).atStartOfDay();
// ✅ 今日 00:00(业务时区)
LocalDateTime todayStart = LocalDate.now(BIZ_ZONE).atStartOfDay();
// ✅ 与数据库时间字段比较(假设 DB 存的也是业务时区 LocalDateTime)
List<Order> todayOrders = orderRepository.findByCreatedAtAfter(todayStart);
如果 DB 存的是 UTC TIMESTAMP / Instant,必须先把 BIZ 起点转 UTC:
Instant todayStartUtc = LocalDate.now(BIZ_ZONE)
.atStartOfDay(BIZ_ZONE)
.toInstant();
with(DayOfWeek.X).atStartOfDay() 链路前是否带 BIZ_ZONE场景: JDBC 默认 serverTimezone 与 JVM 不一致;MySQL TIMESTAMP 自动转换与 DATETIME 不同步
TIMESTAMP 会按 session/server 时区做存读转换;DATETIME 是裸字符串serverTimezone=UTC 时,Java 写入 2026-05-14 10:00 BIZ 实际存为 UTC,读出来又转一次UTC 但 DB session 用 +08:00,会出现"今天的数据"在 WHERE created_at >= today 查不到固定时区策略并文档化(团队内统一选一种):
| 策略 | DB 列类型 | JDBC URL | Java 字段 | 适用场景 |
|---|---|---|---|---|
| A. 业务时区全栈 | DATETIME | serverTimezone=Asia/Shanghai | LocalDateTime | 单一时区业务(推荐 90% 中国场景) |
| B. UTC 存储 + 业务展示 | TIMESTAMP | serverTimezone=UTC | Instant / OffsetDateTime | 跨时区 SaaS、需要全球部署 |
强烈禁止混用:DB 列用 DATETIME 又把 serverTimezone=UTC,最难排查。
# ✅ 策略 A 的连接串示例
spring:
datasource:
url: jdbc:mysql://host:3306/db?serverTimezone=Asia/Shanghai&useLegacyDatetimeCode=false
# ✅ 部署层固定 JVM 时区,避免 base image 飘
ENV TZ=Asia/Shanghai
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
serverTimezoneTZ 环境变量DATETIME vs TIMESTAMP)与策略一致场景: 用户上传"2026-05-14 10:00"字符串,后端转换时丢失时区信息
// ❌ 错误:字符串直接 parse 成 LocalDateTime,时区信息丢失
LocalDateTime userTime = LocalDateTime.parse(request.getTimeStr());
// ❌ 错误:与 Instant 比较时隐式用 JVM 时区
Instant dbTime = order.getCreatedInstant();
LocalDateTime userTime = LocalDateTime.parse(request.getTimeStr());
if (userTime.isAfter(LocalDateTime.ofInstant(dbTime, ZoneId.systemDefault()))) { ... }
// ✅ 明确字符串是业务时区
LocalDateTime userLocal = LocalDateTime.parse(request.getTimeStr());
Instant userInstant = userLocal.atZone(BIZ_ZONE).toInstant();
// ✅ 比较时都转 Instant
if (userInstant.isAfter(order.getCreatedInstant())) { ... }
Instant,不用 LocalDateTime 直接比yyyy-MM-dd HH:mm:ss z)// ❌ 依赖系统时区
now := time.Now()
// ✅ 业务时区
var bizZone = time.FixedZone("Asia/Shanghai", 8*3600)
// 或加载 IANA
bizZone, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(bizZone)
weekStart := now.AddDate(0, 0, -int(now.Weekday()-time.Monday)).
Truncate(24 * time.Hour)
# ❌ naive datetime
from datetime import datetime
now = datetime.now()
# ✅ aware datetime
from datetime import datetime
from zoneinfo import ZoneInfo
BIZ_ZONE = ZoneInfo("Asia/Shanghai")
now = datetime.now(tz=BIZ_ZONE)
week_start = (now - timedelta(days=now.weekday())).replace(hour=0, minute=0, second=0, microsecond=0)
// ❌ Date 默认本地时区
const now = new Date();
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
// ✅ 用 luxon 或 date-fns-tz 显式时区
import { DateTime } from "luxon";
const BIZ_ZONE = "Asia/Shanghai";
const now = DateTime.now().setZone(BIZ_ZONE);
const weekStart = now.startOf("week"); // luxon 默认 Monday
const todayStart = now.startOf("day");
// 落库前转 UTC ISO
const utcIso = now.toUTC().toISO();
应用层:
BIZ_ZONE / BIZ_LOCATION 常量LocalDate.now() / LocalDateTime.now() / new Date() / time.Now()Clock.fixed() / Mock,不依赖真实时间数据层:
serverTimezoneInstant / UTC 时间戳部署层:
TZ 环境变量接口层:
2026-05-14T10:00:00+08:00)> 📋 本回复遵循:`time-zone-safety` - [章节名]
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
当设计或修改 REST API 响应结构、处理 API 返回值时触发。防止 API 设计缺陷导致的字段错位、类型歧义等问题。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
当设计或修改 REST API 响应结构、处理 API 返回值,或生成 Excel/CSV/PDF/对账文件等下游产物时触发。防止 API 设计缺陷导致的字段错位、类型歧义,以及生成产物时关键字段缺失但静默成功的问题。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
网关/代理/WAF/CDN 中间件的安全关键词匹配实现规范,防止纯子串匹配误判正常响应内容中的技术术语(如 Cloudflare、502、error)
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
当 API/任务可能执行超过 10 秒(批量数据处理、远程 API 批量调用、全表扫描、跨租户聚合)时触发。防止同步接口被网关 30s 超时切断、用户重复点击触发并发、状态缓存内存泄漏等问题。提供异步任务状态机标准模板。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
当用户操作 .sh、Dockerfile、Makefile、.yml、.yaml 文件,或在 Markdown 中编写 bash 代码块时触发。提供 Bash 编写规范。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Bash 脚本与系统命令规范。禁止行尾注释,强制使用 tee/heredoc 写入,提升命令执行的可维护性。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.