本文目录导读:

在Java中进行日期处理时,要统一调用流程,核心目标是解决以下痛点:
- 线程安全:旧API (
java.util.Date,SimpleDateFormat) 非线程安全。 - 代码混乱:混用
Date、Calendar、SimpleDateFormat和java.time包。 - 时区混乱:不同环境(服务器、用户)时区不一致。
- 格式不一致:到处硬编码
yyyy-MM-dd HH:mm:ss。
以下是推荐的统一标准化流程,基于 Java 8+ 的 java.time 包。
核心原则:统一使用 java.time
- 存储/交互:使用
Instant(时间戳) 或String(ISO 8601标准格式)。 - 业务逻辑/计算:使用
LocalDate、LocalDateTime、ZonedDateTime。 - 显示/输入:使用
DateTimeFormatter。 - 数据库交互:通过
PreparedStatement.setObject()/ResultSet.getObject()直接读写java.time对象。
统一调用流程(三层架构)
接收外部输入(Controller层 / 前端)
- 原则:统一接收 ISO 8601 字符串 或 毫秒时间戳。
// 1.1 接收 ISO 8601 字符串
public void receiveDate(String dateStr) {
// 统一解析为 Instant(UTC时间)
Instant instant = Instant.parse(dateStr); // "2023-10-27T10:30:00Z"
// 或者解析为特定时区的 ZonedDateTime
ZonedDateTime zdt = ZonedDateTime.parse(dateStr);
}
// 1.2 接收时间戳 (Long)
public void receiveTimestamp(Long timestamp) {
Instant instant = Instant.ofEpochMilli(timestamp);
}
业务处理(Service层)
- 原则:内部统一用
LocalDateTime(业务无关时区)或ZonedDateTime(有时区需求)。
@Service
public class OrderService {
public void processOrder(Instant createInstant) {
// 转换为系统默认时区进行业务计算
ZonedDateTime beijingTime = createInstant.atZone(ZoneId.of("Asia/Shanghai"));
LocalDateTime ldt = beijingTime.toLocalDateTime();
// 业务逻辑:计算 +3天
LocalDateTime dueDate = ldt.plusDays(3);
// 如果需要返回,转换为 Instant 或标准字符串
Instant dueInstant = dueDate.atZone(ZoneId.of("Asia/Shanghai")).toInstant();
}
}
持久化(DAO层 / 数据库)
- 原则:使用
PreparedStatement.setObject()/ResultSet.getObject(),无需手动转换。
// 3.1 写入数据库
PreparedStatement ps = conn.prepareStatement("INSERT INTO orders (create_time) VALUES (?)");
ps.setObject(1, LocalDateTime.now()); // 数据库字段类型 TIMESTAMP/TIMESTAMP WITHOUT TIME ZONE
// 3.2 读取数据库
ResultSet rs = stmt.executeQuery("SELECT create_time FROM orders");
LocalDateTime ldt = rs.getObject("create_time", LocalDateTime.class);
// 3.3 如果需要UTC存储,使用 Instant
ps.setObject(1, Instant.now()); // 数据库字段类型 TIMESTAMP WITH TIME ZONE
输出到外部(Response / 展示层)
- 原则:统一输出为 ISO 8601 字符串(推荐)或 时间戳。
// 4.1 输出为 ISO 8601 标准字符串(推荐)
Instant now = Instant.now();
String isoString = now.toString(); // 输出: "2023-10-27T02:30:00Z"
// 4.2 输出为特定格式的字符串
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = ldt.format(formatter);
// 4.3 使用 Jackson 自动序列化(配置全局格式)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private LocalDateTime createTime;
关键工具封装:统一转换器
为了避免业务代码中到处写 DateTimeFormatter 和 ZoneId,建议创建工具类:
import java.time.*;
import java.time.format.DateTimeFormatter;
public final class DateUtils {
private static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Shanghai");
private static final DateTimeFormatter DEFAULT_FORMATTER
= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 1. LocalDateTime -> 格式化的字符串
public static String format(LocalDateTime ldt) {
return ldt.format(DEFAULT_FORMATTER);
}
// 2. 字符串 -> LocalDateTime (按指定格式)
public static LocalDateTime parse(String dateStr, String pattern) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return LocalDateTime.parse(dateStr, formatter);
}
// 3. LocalDateTime <-> Instant (指定时区)
public static Instant toInstant(LocalDateTime ldt) {
return ldt.atZone(DEFAULT_ZONE).toInstant();
}
public static LocalDateTime fromInstant(Instant instant) {
return LocalDateTime.ofInstant(instant, DEFAULT_ZONE);
}
// 4. Date (遗留系统) <-> LocalDateTime (桥接)
public static LocalDateTime fromDate(Date date) {
return date.toInstant().atZone(DEFAULT_ZONE).toLocalDateTime();
}
public static Date toDate(LocalDateTime ldt) {
return Date.from(ldt.atZone(DEFAULT_ZONE).toInstant());
}
}
| 场景 | 推荐类型 | 为什么? |
|---|---|---|
| 系统内部计算 | LocalDateTime |
无时区干扰,简洁高效 |
| 跨时区系统 | Instant 或 ZonedDateTime |
保证时间唯一性,避免时区错误 |
| 数据库存储 | TIMESTAMP + LocalDateTime |
与 java.time 对应,无需转换 |
| 前端交互 | String (ISO 8601格式) |
通用性强,前端友好 |
| 遗留系统对接 | Date + SimpleDateFormat |
桥接旧API,但需注意线程安全 |
特别避坑提醒
- 禁止在代码中混用
Date和LocalDateTime,如果必须使用(如第三方库返回Date),立即转换为LocalDateTime再使用。 - 禁止使用
SimpleDateFormat,如果必须使用(如旧框架),请创建局部变量或使用ThreadLocal。 - 统一时区配置:在应用启动时设置默认时区,避免依赖服务器环境变量。
@PostConstruct public void init() { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); }
遵循这个流程,你的日期处理将变得可预测、线程安全、易于维护。