System.currentTimeMillis()返回自UTC时间1970-01-01的毫秒数,与时区无关;应通过Instant.ofEpochMilli(millis).atZone(ZoneId.of("Asia/Shanghai")).toLocalDateTime()转换为本地时间,避免使用过时的Date和SimpleDateFormat。

System.currentTimeMillis() 返回的是一个与时区无关的毫秒数,它本身不包含任何时间显示信息。要把它转成你所在地区看得懂的“本地时间”,关键不是“转换数值”,而是“正确解释这个数值在你时区下的含义”。
理解时间戳的本质
这个方法返回的是从 1970-01-01 00:00:00 UTC 开始经过的毫秒数。全球任何地方调用,同一时刻得到的数字完全一样。它就像一把尺子上的刻度,本身没有“几点钟”的意思——只有配上时区,才能读出“北京时间上午9点”或“伦敦时间凌晨1点”。
- 不要把它直接当成“本地毫秒数”,它从来就不是
- new Date(timestamp) 会自动按系统默认时区渲染,但 Date 类已过时,不推荐用于新逻辑
- LocalDateTime.now() 是另一条路:它不基于时间戳,而是直接读取系统时钟+默认时区,结果可能和 timestamp 对应的本地时间一致,也可能因系统时区设置不同而偏差
推荐做法:用 Instant + ZoneId 构建本地时间
这是 Java 8+ 中清晰、可控的方式。核心思路是:先把这个毫秒数当作 UTC 时间点(Instant),再指定你要的时区(比如 Asia/Shanghai),最后提取本地视图(LocalDateTime)。
- 代码示例:
long millis = System.currentTimeMillis();
LocalDateTime local = Instant.ofEpochMilli(millis)
.atZone(ZoneId.systemDefault()) // 或 ZoneId.of("Asia/Shanghai")
.toLocalDateTime();
- 这样写逻辑明确:毫秒 → UTC 瞬间 → 某一时区的本地时间 → 仅含日期时间(无时区)
- 避免了 new Date() + SimpleDateFormat 的线程安全和设计陈旧问题
- 如果需要带时区的完整表示,用 ZonedDateTime 替代 toLocalDateTime()
格式化输出要注意 Locale 和模式
有了 LocalDateTime 后,要显示成“2026-06-15 01:50:33”这类字符串,得靠 DateTimeFormatter。它不关心时区,只负责把年月日时分秒按规则拼出来。
- 基础格式:
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String s = local.format(f);
- 如需适配多语言(比如星期几、月份名),加上 Locale 参数:
- DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss", Locale.CHINA);
- 避免用 new SimpleDateFormat,它是非线程安全的,且 API 设计不够直观
常见陷阱提醒
很多问题其实不是“转换错了”,而是对概念混淆导致的:
- 误以为 LocalDateTime.now() 和 System.currentTimeMillis() 转出来的结果一定相等 —— 它们依赖的底层时钟源和时区解析路径不同,微秒级差异正常
- 跨服务传递时间时,只传 LocalDateTime —— 它不含时区,接收方无法还原原始时刻,应传 Instant 或带时区的字符串(如 ISO 8601 格式)
- 在服务器部署时忽略容器/OS 时区设置 —— ZoneId.systemDefault() 会受运行环境影响,生产环境建议显式指定 ZoneId.of("Asia/Shanghai")


















