今天在浏览Gitee的时候,我看到了RuoYi-Vue-Plus
的一个合并请求update:弃用java.util.Date改为java8 java.time.Instant。显然在 Java 开发中,处理时间是一个非常常见的需求。然而,在 Java 的发展过程中,出现了多个用于处理日期和时间的类,比如 Date
、Instant
和 LocalDateTime
。它们分别来自不同的 API 版本,用途和适用场景也有所不同。
本文将从定义、特点、适用场景以及在 Spring Boot 项目中的使用实例等方面,对这三个常用的时间处理类进行全面分析和对比,帮助开发者更好地选择合适的类型来处理时间相关问题。
一、概述对比表
类名 | 所属包 | 精度 | 是否包含时区 | 是否可变 | 推荐程度 |
---|---|---|---|---|---|
java.util.Date | java.util | 毫秒 | ❌(内部存储为 UTC 时间戳) | ✅ 可变 | ⚠️ 不推荐 |
java.time.Instant | java.time | 纳秒 | ✅ UTC 时间戳 | ❌ 不可变 | ✅ 推荐 |
java.time.LocalDateTime | java.time | 纳秒 | ❌ 无 | ❌ 不可变 | ✅ 推荐 |
二、逐个解析
java.util.Date
- 所属 API: Java 1.0 原始时间 API
- 特点:
- 内部用的是一个毫秒级的时间戳(从 1970-01-01T00:00:00Z 开始计算)
- 存在线程安全问题(如
SimpleDateFormat
) - 被标记为过时,不推荐在新项目中使用
- 适用场景:
- 旧系统维护或与遗留代码交互
- 需要兼容老版本 JDK 的环境
示例代码(Spring Boot Controller):
@RestController
public class DateController {
@GetMapping("/date")
public String getDate() {
Date date = new Date();
return "Current Date: " + date.toString();
}
}
java.time.Instant
- 所属 API: Java 8 引入的
java.time
包 - 特点:
- 表示的是一个“时间线上的瞬间点”
- 精度为纳秒,常用于记录事件发生的真实时间点
- 是不可变对象,线程安全
- 适用场景:
- 日志记录、审计日志、数据库插入时间等需要精确时间点的场景
示例代码(Spring Boot Controller):
@RestController
public class InstantController {
@GetMapping("/instant")
public String getInstant() {
Instant now = Instant.now();
return "Current Instant (UTC): " + now;
}
}
输出结果类似:
Current Instant (UTC): 2025-04-05T10:00:00.123Z
java.time.LocalDateTime
- 所属 API: Java 8 的
java.time
包 - 特点:
- 表示本地日期时间(年月日+时分秒),不包含时区信息
- 更适合显示给用户看的时间格式
- 适用场景:
- 展示给用户的业务时间
- 与时区无关的本地逻辑判断
示例代码(Spring Boot Controller):
@RestController
public class LocalDateTimeController {
@GetMapping("/localdatetime")
public String getLocalDateTime() {
LocalDateTime now = LocalDateTime.now();
return "Current LocalDateTime: " + now;
}
}
输出结果取决于运行环境的时区:
Current LocalDateTime: 2025-04-05T18:00:00.123
三、类型转换详解(Spring Boot 常见操作)
Instant
↔ Date
Date date = Date.from(Instant.now());
Instant instant = date.toInstant();
Instant
↔ LocalDateTime
需要通过 ZoneId
来指定时区:
import java.time.ZoneId;
// Instant -> LocalDateTime
Instant now = Instant.now();
LocalDateTime localDateTime = LocalDateTime.ofInstant(now, ZoneId.systemDefault());
// LocalDateTime -> Instant
Instant convertedBack = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date
↔ LocalDateTime
// Date -> LocalDateTime
Date date = new Date();
LocalDateTime ldt = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
// LocalDateTime -> Date
Date convertedBack = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
四、Spring Boot 数据库映射建议
Java 类型 | 推荐数据库字段类型 | 是否含时区信息 | 说明 |
---|---|---|---|
Instant | TIMESTAMP WITH TIME ZONE (PostgreSQL)TIMESTAMP (MySQL) | ✅ 是 | 表示绝对时间点 |
LocalDateTime | DATETIME 或 TIMESTAMP WITHOUT TIME ZONE | ❌ 否 | 表示本地时间 |
实体类示例
@Entity
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Instant createdAt; // 对应 TIMESTAMP WITH TIME ZONE
private LocalDateTime updatedAt; // 对应 DATETIME / TIMESTAMP WITHOUT TIME ZONE
// getter/setter
}
MySQL 建表语句示例
CREATE TABLE my_entity (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT NULL
);
五、最佳实践总结
场景 | 推荐类 |
---|---|
日志记录、事件时间点 | Instant |
用户界面展示时间 | LocalDateTime |
数据库存储时间戳 | Instant (带时区)或 LocalDateTime (不带时区) |
与前端交互(JSON) | LocalDateTime (结合时区转换) |
与遗留系统交互 | Date (但尽量封装转换逻辑) |
六、Spring Boot 平滑过渡到 Instant / LocalDate / LocalDateTime
如果你希望返回的 LocalDateTime
显示为某种格式,比如 "2025-04-05 18:00:00"
,可以使用注解来接受返回指定格式的时间:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Instant instant;
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate localDate;
或者自定义配置:
@Configuration
public class GlobalDateTimeConfig implements Jackson2ObjectMapperBuilderCustomizer {
private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
private static final String DATE_DATE_PATTERN = "yyyy-MM-dd";
private static final ZoneId ZONE_ID = ZoneId.of("Asia/Shanghai");
@Override
public void customize(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
jackson2ObjectMapperBuilder.simpleDateFormat(DATE_TIME_PATTERN).timeZone(TimeZone.getTimeZone(ZONE_ID));
// 自定义时间序列化模块
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(LocalDateTime.class, new JsonSerializer<>() {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN).format(value));
}
});
simpleModule.addSerializer(LocalDate.class, new JsonSerializer<>() {
@Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(DateTimeFormatter.ofPattern(DATE_DATE_PATTERN).format(value));
}
});
simpleModule.addSerializer(Instant.class, new JsonSerializer<>() {
@Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN).withZone(ZONE_ID).format(value));
}
});
jackson2ObjectMapperBuilder.modules(simpleModule);
}
}
而且 application.yml
中配置不会对 Instant / LocalDate / LocalDateTime 生效:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
上述配置 spring.jackson.date-format 是针对 Date 起作用,如果需要转换 Instant / LocalDate / LocalDateTime 。需要引入上述注解或者重写 JackJson 配置
七、结语
Java 的时间处理 API 在不断发展和完善,Date
已被时代淘汰,取而代之的是功能更强大、设计更合理的 java.time
包中的 Instant
和 LocalDateTime
。它们各自有明确的职责划分,在实际开发中应根据具体业务需求合理选用。
评论区