生产环境必须显式设置时区,-Duser.timezone=Asia/Shanghai是最可靠方式,因JVM优先读取该参数而非系统时区;Alpine镜像需同时安装tzdata并配置/etc/localtime和/etc/timezone,否则date命令与Java时间均错为UTC。

生产环境必须显式设置时区,不能依赖系统默认或 JVM 自动探测 —— OpenJDK(尤其是 Alpine 基础镜像)默认时区是 GMT,不是 Asia/Shanghai,Java 应用里 new Date()、LocalDateTime.now()、日志时间戳全会错 8 小时。
为什么 -Duser.timezone=Asia/Shanghai 是最可靠的方式
OpenJDK 启动时读取 user.timezone 系统属性的优先级高于 OS 层时区(如 /etc/localtime 或 TZ 环境变量)。Alpine 镜像里即使你挂载了宿主机 /etc/localtime,Java 仍可能 fallback 到 GMT,因为 tzdata 包缺失导致 getSystemTimeZoneID() 返回 null,最终走硬编码的 GMT_ID。
实操建议:
- 在
java -jar启动命令中**强制加上**-Duser.timezone=Asia/Shanghai,不要省略 - Spring Boot 用户:把该参数加到
JAVA_OPTS或ENTRYPOINT中,别只写在application.properties里(后者只影响 Spring 的时区逻辑,不影响TimeZone.getDefault()) - 避免用
-Duser.timezone=GMT+8:这种偏移写法不支持夏令时,且部分 JDK 版本解析不稳定
Docker 容器里必须同时配 tzdata + JVM 参数
只设 JVM 参数能解决 Java 层时间,但 date 命令、Shell 脚本、健康检查脚本等仍显示 UTC。Alpine 镜像默认不含完整时区数据,apk add tzdata 是必要步骤。
实操建议:
- Dockerfile 中添加:
RUN apk add --no-cache tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone - 务必在
apk add tzdata后立即cp和echo,否则apk del tzdata会删掉时区文件 - 如果用
eclipse-temurin或amazoncorretto替代openjdk:8-jdk-alpine,它们已内置tzdata,可跳过apk add步骤
Spring Boot 应用里 LocalDateTime.now() 还是 GMT?检查这三点
即使 JVM 时区设对了,Spring Boot 应用仍可能打印出 UTC 时间,常见于日志、JSON 序列化、数据库写入等场景。
排查要点:
- 确认
spring.jackson.time-zone配置为Asia/Shanghai(影响 JSON 时间序列化) - 检查数据库连接 URL 是否带
serverTimezone=Asia/Shanghai(MySQL)或timezone=Asia/Shanghai(PostgreSQL) - 验证日志框架(Logback/Log4j2)是否设置了
%d{yyyy-MM-dd HH:mm:ss.SSS,Asia/Shanghai}这类带时区的 pattern
最容易被忽略的是:Kubernetes Pod 启动后,env | grep TZ 可能看不到 TZ,但这不代表它没生效 —— JVM 在启动瞬间就读取了 -Duser.timezone,后续改环境变量完全无效。所以别在运行中试图用 kubectl exec 改 TZ,得重建 Pod。

















