1. 概述

本文将介绍如何在 Java 7、Java 8 以及 Joda-Time 库中为日期设置时区。内容简洁实用,适合有经验的开发者快速查阅,避免踩坑。

✅ 核心目标:把一个时间点(Instant)转换为指定时区下的本地时间表示
⚠️ 注意:java.util.Date 本身不保存时区信息,时区只是在格式化或转换时才起作用


2. 使用 Java 8 新日期时间 API

Java 8 引入了全新的 java.time 包,设计灵感来自 Joda-Time,解决了旧 API 的诸多缺陷。

关键类说明:

  • Instant:表示 UTC 时间轴上的一个瞬时点(从 1970-01-01T00:00:00Z 开始的纳秒数)
  • ZoneId:时区标识,如 "Asia/Shanghai"
  • ZonedDateTime:带时区的日期时间,ISO-8601 标准

示例代码

Instant nowUtc = Instant.now(); // 当前 UTC 时间
ZoneId asiaSingapore = ZoneId.of("Asia/Singapore");

ZonedDateTime nowAsiaSingapore = ZonedDateTime.ofInstant(nowUtc, asiaSingapore);

输出示例:

2024-09-04T12:30:45.123+08:00[Asia/Singanghai]

📌 小贴士:ZonedDateTime 是线程安全的,推荐在现代 Java 项目中使用。


3. 使用 Java 7 老旧 API

Java 7 的日期处理堪称“历史遗留问题”,Date 类本身不包含时区信息 ❌,必须借助 Calendar 才能体现时区含义。

步骤如下:

  1. 获取当前时间(本质仍是 UTC 毫秒值)
  2. 获取目标时区 TimeZone
  3. 使用 Calendar.getInstance(timeZone) 创建带时区的日历对象
  4. 设置时间值

示例代码

Date nowUtc = new Date();
TimeZone asiaSingapore = TimeZone.getTimeZone("Asia/Singapore");

Calendar nowAsiaSingapore = Calendar.getInstance(asiaSingapore);
nowAsiaSingapore.setTime(nowUtc);

⚠️ 踩坑提醒:

  • Calendar 是可变对象,非线程安全
  • 多线程环境下容易出问题,建议封装成不可变对象或使用 Java 8+
  • Date.toString() 默认使用 JVM 本地时区输出,容易误导人

✅ 建议:除非维护老系统,否则不要在新项目中使用 Java 7 的日期 API。


4. 使用 SimpleDateFormat 处理带时区的字符串

在对接老系统或日志解析场景中,经常需要将 Date 格式化为带时区的字符串,这时 SimpleDateFormat 仍有一席之地。

关键点:

  • 模式字符 Z 表示带偏移量的时区(如 +0800)
  • 必须通过 setTimeZone() 显式设置格式化器的时区

示例代码

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata")); // IST (+05:30)

Date date = new Date(1725437542000L); // 对应 2024-09-04 08:12:22 UTC
Assert.assertEquals("2024-09-04 13:42:22 +0530", sdf.format(date));

📌 解释:UTC 时间 08:12 加上 5.5 小时 → 印度时间 13:42

⚠️ 注意事项:

  • SimpleDateFormat 非线程安全,高并发下必须用 ThreadLocal 包装或改用 DateTimeFormatter
  • 格式化时的时区仅影响输出,不改变 Date 本身的毫秒值

5. 使用 Joda-Time(Java 7 环境下的优雅选择)

如果无法升级到 Java 8,Joda-Time 是最佳替代方案 ✅。它曾是 Java 社区事实上的日期处理标准,API 设计清晰,功能强大。

添加依赖

<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10</version>
</dependency>

核心类说明

  • Instant:表示时间轴上的瞬时点(毫秒级精度)
  • DateTimeZone:时区对象
  • DateTime:带时区的日期时间,功能类似 Java 8 的 ZonedDateTime

示例代码

Instant nowUtc = Instant.now();
DateTimeZone asiaSingapore = DateTimeZone.forID("Asia/Singapore");

DateTime nowAsiaSingapore = nowUtc.toDateTime(asiaSingapore);

输出示例:

2024-09-04T20:30:45.123+08:00

✅ 优势:

  • API 简洁直观
  • 支持丰富的时区操作和日期计算
  • 社区支持良好,文档齐全

📌 迁移建议:若未来升级 Java 8,可用 DateTime.toInstant() 平滑过渡到 java.time.Instant


6. 总结

方案 推荐程度 适用场景
Java 8 ZonedDateTime ✅✅✅ 新项目首选,线程安全,API 清晰
Joda-Time ✅✅ Java 7 环境下的最佳选择
SimpleDateFormat + Calendar ⚠️ 仅用于兼容老系统或格式化输出
java.util.Date 直接操作 避免使用,易出错

📌 最佳实践建议:

  • 统一使用 UTC 存储时间戳
  • 展示给用户时再转换为本地时区
  • 日志中记录时间应包含时区信息
  • 避免依赖 JVM 默认时区(可通过 -Duser.timezone=UTC 强制设置)

所有示例代码已上传至 GitHub:https://github.com/baeldung/tutorials/tree/master/core-java-modules/core-java-8-datetime


原始标题:Set the Time Zone of a Date in Java | Baeldung