Java时区转换核心是区分“时刻”与“本地时间显示”,推荐用ZonedDateTime建模时区上下文,或以Instant为中间标准;禁用Date/Calendar及手动加减小时,须用IANA时区ID。

Java 中在不同时区之间转换日期时间,核心是区分“时刻”和“本地时间显示”:同一个瞬时(Instant)在不同地区显示为不同的本地时间(如北京时间 10:00 和纽约时间 22:00 可能是同一时刻)。关键不是“加减几小时”,而是用 ZoneId 和 ZonedDateTime 正确建模时区上下文。
用 ZonedDateTime 进行带时区的转换
这是最推荐、语义最清晰的方式。它明确绑定时间、时区和夏令时规则:
- 从本地时间构造:比如“2024-05-20T14:30 在上海”,先用
LocalDateTime+ZoneId.of("Asia/Shanghai")得到ZonedDateTime - 转换到另一时区:调用
withZoneSameInstant(ZoneId.of("America/New_York"))—— 保持同一物理时刻,只改变显示 - 获取结果:用
toLocalDateTime()或format(...)提取目标时区下的本地时间
示例:
ZonedDateTime shanghai = LocalDateTime.of(2024, 5, 20, 14, 30).atZone(ZoneId.of("Asia/Shanghai"));
ZonedDateTime ny = shanghai.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(ny.toLocalDateTime()); // 输出:2024-05-20T02:30
用 Instant 作为中间标准时刻
当源头是时间戳(如数据库 long 值、API 返回的秒数)或需要跨系统统一基准时,先转成 Instant(UTC 瞬时),再转目标时区:
立即学习“Java免费学习笔记(深入)”;
Instant instant = Instant.ofEpochMilli(1716215400000L);ZonedDateTime tokyo = instant.atZone(ZoneId.of("Asia/Tokyo"));ZonedDateTime london = instant.atZone(ZoneId.of("Europe/London"));
这种方式天然规避了夏令时歧义,因为 Instant 本身不含时区,只代表自 Unix 纪元以来的毫秒数。
避免使用已废弃的 Date/Calendar 和简单加减
java.util.Date 内部存储的是 UTC 毫秒值,但其 toString() 方法会按 JVM 默认时区格式化,容易造成误解;Calendar 手动 set() 时区后操作易出错,且不处理夏令时自动切换。
- 不要写
date.setTime(date.getTime() + 8 * 60 * 60 * 1000)来“加8小时”——这忽略夏令时、时区偏移历史变更(如中国 1992 年前用过夏令时) - 不要依赖
SimpleDateFormat的setTimeZone()做转换,它只影响格式化/解析行为,不改变时间本质
注意时区 ID 的正确写法
始终使用 IANA 时区 ID(如 "Asia/Shanghai"),而不是缩写(如 "CST" 或 "PST"):
- 缩写不唯一:
CST可能指 China Standard Time、Central Standard Time(美国)、Cuba Standard Time - IANA ID 包含完整地理和历史规则,JVM 能自动处理历年夏令时调整
- 可用
ZoneId.getAvailableZoneIds()查看系统支持的全部时区


















