← 返回题目列表

Java 8 日期时间 API 有哪些核心类?如何正确处理时区?

高频 中等 第 3 / 24 题 更新于 2026/07/26
java.timeInstantLocalDateTimeZonedDateTime

简化版

java.time 的核心类型不可变且线程安全:Instant 表示时间线上的瞬间,LocalDateTime 表示不含时区的本地日期时间,ZonedDateTime 同时包含地区时区规则。跨系统记录时间通常保存 Instant,展示或业务计算时再结合明确的 ZoneId,不能把 LocalDateTime 当成全球唯一时间点。

详细版

LocalDateLocalTimeLocalDateTime 适合生日、营业时间等本地概念;Instant 适合时间戳;OffsetDateTime 带固定 UTC 偏移;ZonedDateTimeAsia/Shanghai 这类地区时区及其历史、夏令时规则。Duration 表达基于秒和纳秒的时间量,Period 表达年、月、日的日期量。

新版类型和 DateTimeFormatter 都是不可变、线程安全的,避免了共享 SimpleDateFormat 的并发问题。旧 Date 可通过 toInstant() 转换;从不含时区的 LocalDateTime 得到时间点时,必须先明确 ZoneId,并考虑夏令时导致的本地时间重复或不存在。

完整版教学

一、先区分时间点与本地时间

Instant 位于 UTC 时间线,能够唯一定位一个瞬间,适合日志时间、事件创建时间和跨服务传输。LocalDateTime 只有年月日时分秒,没有偏移量和时区规则,同一个值在上海和纽约对应不同瞬间。

Instant createdAt = Instant.now();
ZoneId zone = ZoneId.of("Asia/Shanghai");
ZonedDateTime displayTime = createdAt.atZone(zone);

不要用字符串拼接 +08:00 代替时区建模。固定偏移只描述某一刻与 UTC 的差值,地区时区还包含历史变更和夏令时规则。

二、核心类型如何选

  • LocalDate:只有日期,如生日、账单日。
  • LocalTime:只有本地时间,如每日开门时间。
  • LocalDateTime:本地日期加时间,但不能独立确定时间点。
  • Instant:UTC 时间线上的瞬间,适合存储和机器间交换。
  • OffsetDateTime:日期时间加固定偏移,可确定瞬间,但不保留地区规则。
  • ZonedDateTime:日期时间加 ZoneId,适合按地区规则进行展示和日历运算。

选择类型要从业务语义出发。会议安排若强调“纽约当地上午九点”,需要保存地区时区;服务器事件若只关心发生顺序,保存 Instant 更直接。

三、Duration 与 Period 不能混用

Duration 基于秒和纳秒,适合“经过 30 分钟”等时间线上的长度;Period 基于年、月、日,适合“一个月后”这类日历运算。一个月的天数不固定,因此不能简单把 Period 当成固定秒数。

Instant deadline = Instant.now().plus(Duration.ofMinutes(30));
LocalDate nextMonth = LocalDate.now().plus(Period.ofMonths(1));

跨夏令时切换时,“加 24 小时”和“本地日期加 1 天”也可能产生不同的当地时刻,必须按业务要表达的语义选择。

四、格式化、解析与旧 API 转换

DateTimeFormatter formatter =
        DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
LocalDateTime value = LocalDateTime.parse("2026-07-19 10:30:00", formatter);

Instant instant = legacyDate.toInstant();
Date date = Date.from(instant);

DateTimeFormatter 可安全复用,不需要像 SimpleDateFormat 那样为并发访问加锁。解析输入时还应明确 Locale 和格式,面向协议交换则优先使用 ISO 标准格式。

五、时区与夏令时陷阱

某些地区进入夏令时时,墙上时钟会向前跳,部分 LocalDateTime 根本不存在;退出夏令时时,一段本地时间又会出现两次。将 LocalDateTime 与 ZoneId 组合时,API 会按时区规则解析这种 gap 或 overlap,但默认结果未必符合业务选择。

涉及预约、航班、交易截止时间时,应明确验证规则和偏移选择。测试也不要到处直接调用系统默认时区或 now();可显式传入 ZoneIdClock,让时区、当前时间和边界场景可控。

六、用夏令时数字看 gap 与 overlap

America/New_York 为例,2024-03-10 从 01:59:59 跳到 03:00:00,本地的 02:30 不存在,这叫 gap;2024-11-03 时钟从 01:59:59 回拨到 01:00:00,本地 01:30 会对应两个不同偏移,这叫 overlap。相同 LocalDateTime 因此可能对应 0、1 或 2 个有效 offset,不能机械地拼时区后就假定结果唯一。

2024-03-10 02:30 America/New_York → gap,墙上时间不存在
2024-11-03 01:30 America/New_York → overlap,可能对应两个瞬间
业务语义建议类型原因
事件绝对发生时刻Instant全球时间线上唯一
每天 09:00 营业LocalTime + 地区规则是当地日历概念
带固定偏移的协议字段OffsetDateTime可确定瞬间并保留 offset
纽约当地会议ZonedDateTime需要地区历史/DST 规则

七、存储、比较与测试边界

数据库列名叫 timestamp 并不能说明它是否保存时区,必须确认数据库类型、驱动和 ORM 映射。跨服务传输优先使用明确的 ISO-8601 表示或 epoch + 单位,不能一边发送秒、一边按毫秒解析;1,700,000,000 秒和毫秒相差 1,000 倍。

测试中将 Clock 作为依赖传入,可用固定 Clock 重现月末、闰日和超时边界。时区数据库规则也可能随政府政策更新,因此面向未来的预约若只保存 Instant,会丢失“当地上午九点”的原始意图;必要时同时保存 LocalDateTime、ZoneId 和业务规则版本。

记忆钩子:Instant 回答“哪一刻”,Local 类型回答“钟表上写什么”,ZoneId 回答“当地钟表如何映射到时间线”。

八、常见误区与追问

  • 误区:LocalDateTime 自带系统默认时区。 它完全不含时区或偏移,只有在转换时显式补充规则才可定位 Instant。
  • 误区:+08:00Asia/Shanghai 永远等价。 offset 是固定差值,地区 ZoneId 还携带历史和未来规则。
  • 误区:一天始终等于 24 小时。 日历加一天与时间线加 24 小时跨 DST 时可能得到不同当地时刻。
  • 误区:DateTimeFormatterSimpleDateFormat 一样不能共享。 DateTimeFormatter 不可变且线程安全,可以安全复用。
  • 追问:Period 一个月是多少秒? 没有固定答案,月份天数和时区日历会影响结果;固定时间量应使用 Duration。
  • 追问:为什么测试应注入 Clock? 可固定当前时刻与时区,稳定复现月末、闰日、DST 和超时边界。
  • 追问:预约系统只存 Instant 是否足够? 若业务承诺“某地区当地时间”,规则变化后可能需保留原 LocalDateTime 与 ZoneId 才能重算。

九、加强记忆

先问业务要的是唯一时间点还是当地日历时间:时间点用 Instant,本地概念用 Local 系列,需要地区规则用 ZonedDateTimeDuration 管时间量,Period 管日期量;转换 LocalDateTime 时必须补充时区,并警惕夏令时的空洞和重叠。