Java常用时间操作类库与Instant、Duration

Instant 是基于 UTC 的不可变时间点,需用 toEpochMilli() 转毫秒数;Duration 仅支持 Instant 运算;Instant.parse() 仅接受 ISO-8601 带时区格式;跨系统传时间优先用 Instant,存库前须匹配字段类型。

Java里用 Instant 表示时间点,不是“当前时间字符串”

很多人一看到 Instant.now() 就以为能直接当“时间戳字符串”用,结果打印出来是 2025-05-12T08:23:45.123Z 这种格式,和数据库或前端要的 1715502225123(毫秒级 long)不一致。
关键在于:Instant 是一个不可变的、基于 UTC 的时间点,它本身不带时区偏移,也不提供格式化能力——它只负责精确到纳秒的时间定位。
需要转成毫秒数就用 instant.toEpochMilli();要转成秒级就用 instant.getEpochSecond();反过来从毫秒构造就用 Instant.ofEpochMilli(1715502225123L)

Duration 只算两个 Instant 之间的差,不能加到 LocalDateTime

Duration 是纯时间段(比如“3小时20分钟”),它只支持和 Instant 或另一个 Duration 运算。常见错误是想对 LocalDateTimeDuration

LocalDateTime now = LocalDateTime.now();
LocalDateTime later = now.plus(Duration.ofHours(3)); // 编译失败!

因为 LocalDateTime 没有时区信息,无法确定“3小时

后”对应哪个绝对时刻;而 Instant 可以:
Instant now = Instant.now();
Instant later = now.plus(Duration.ofHours(3)); // ✅ 正确

如果真要操作本地时间,得先转成带时区的类型,比如 ZonedDateTime
ZonedDateTime zdt = LocalDateTime.now().atZone(ZoneId.systemDefault());
ZonedDateTime later = zdt.plus(Duration.ofHours(3));

别再用 DateSimpleDateFormat,但要注意 Instant.parse() 的格式限制

Instant.parse() 只接受 ISO-8601 格式,且必须带时区标识(Z+08:00)。传入 "2025-05-12 08:23:45""2025-05-12T08:23:45" 都会抛 DateTimeParseException
常见适配方式:

  • 如果是无时区的本地时间字符串,先用 LocalDateTime.parse(),再结合时区转成 Instant
    LocalDateTime ldt = LocalDateTime.parse("2025-05-12T08:23:45");
    Instant instant = ldt.atZone(ZoneId.of("Asia/Shanghai")).toInstant();
  • 如果原始字符串带空格分隔,用 DateTimeFormatter 显式指定:
    DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime ldt = LocalDateTime.parse("2025-05-12 08:23:45", f);
记住:Instant 不处理“年月日”逻辑,闰秒、夏令时、月份天数这些都交给 ZonedDateTimeLocalDateTime 去管。

跨系统传时间优先用 Instant,存数据库前确认字段类型

微服务之间传递时间点,统一用 Instant + JSON 序列化(如 Jackson 默认支持),避免各服务用不同时区解析出错。
但写入数据库时容易踩坑:

  • MySQL 的 DATETIME 类型不带时区,JDBC 驱动默认按 JVM 时区解释,可能把 Instant 错转成本地时间;建议显式用 PreparedStatement.setObject(idx, instant, JDBCType.TIMESTAMP_WITH_TIMEZONE)(需 MySQL 8.0+ 和 Connector/J 8.0+)
  • PostgreSQL 的 TIMESTAMP WITH TIME ZONE 字段能正确接收 Instant,但 Hibernate 6 默认映射为 java.time.Instant,5.x 则需手动配置 @Column(columnDefinition = "TIMESTAMP WITH TIME ZONE")
  • 如果数据库字段是 BIGINT 存毫秒值,那就老老实实用 instant.toEpochMilli() 转,别碰字符串中间层
真正麻烦的从来不是怎么表示时间,而是“谁在什么时候、用什么规则解释这个时间”。