427. Java 日期时间 API - ZonedDateTime、OffsetDateTime、OffsetTime

11 阅读3分钟

427. Java 日期时间 API - ZonedDateTime、OffsetDateTime、OffsetTime

1. 三个和时区相关的类 🌍

Java 的 Date-Time API 中,有三个经常会用到的类,它们都能处理 时区/偏移量

  1. ZonedDateTime 👉 包含日期、时间、时区 ID(例如 "Asia/Shanghai")以及对应的 偏移量。 ✅ 最强大,适合需要完整时区规则(比如夏令时)的场景。
  2. OffsetDateTime 👉 包含日期、时间,只有偏移量(例如 +09:00),但没有时区 ID。 ✅ 常用于数据库存储、网络传输,或者只需要绝对偏移的情况。
  3. OffsetTime 👉 只包含时间(小时、分钟、秒),加一个偏移量(+/-HH:mm)。 ✅ 常用于“只关心时间点,不关心日期”的场景,比如每天 UTC+02:00 的提醒。

2. ZonedDateTime ✈️ —— 带时区的完整日期时间

ZonedDateTime 本质上就是: LocalDateTime + ZoneId

  • 它知道自己在哪个城市/地区,并能正确处理 夏令时(DST) 的转换。
  • 比如:从旧金山飞东京 ✈️
import java.time.*;
import java.time.format.DateTimeFormatter;

public class FlightExample {
    public static void main(String[] args) {
        DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM d yyyy  hh:mm a");

        // 起飞时间:2013年7月20日 19:30,旧金山(洛杉矶时区)
        LocalDateTime leaving = LocalDateTime.of(2013, Month.JULY, 20, 19, 30);
        ZoneId leavingZone = ZoneId.of("America/Los_Angeles");
        ZonedDateTime departure = ZonedDateTime.of(leaving, leavingZone);

        System.out.printf("LEAVING:  %s (%s)%n", departure.format(format), leavingZone);

        // 飞行时长:10小时50分钟(650分钟)
        ZoneId arrivingZone = ZoneId.of("Asia/Tokyo");
        ZonedDateTime arrival = departure.withZoneSameInstant(arrivingZone).plusMinutes(650);

        System.out.printf("ARRIVING: %s (%s)%n", arrival.format(format), arrivingZone);

        // 判断到达时是否夏令时
        if (arrivingZone.getRules().isDaylightSavings(arrival.toInstant())) {
            System.out.printf("  (%s daylight saving time will be in effect.)%n", arrivingZone);
        } else {
            System.out.printf("  (%s standard time will be in effect.)%n", arrivingZone);
        }
    }
}

输出:

LEAVING:  Jul 20 2013  07:30 PM (America/Los_Angeles)
ARRIVING: Jul 21 2013  10:20 PM (Asia/Tokyo)
  (Asia/Tokyo standard time will be in effect.)

🎯 场景:跨国航班、会议时间换算、金融交易(需要考虑夏令时)。


3. OffsetDateTime 🕰️ —— 带偏移量的日期时间

OffsetDateTime 本质上是: LocalDateTime + ZoneOffset

  • 它没有时区 ID(比如“北京”),只有绝对偏移(比如 +08:00)。
  • 常用于 数据库存储日志记录网络传输(XML/JSON)
  • 例子:找出 2013 年 7 月的最后一个星期四 📅
import java.time.*;
import java.time.temporal.TemporalAdjusters;

public class OffsetExample {
    public static void main(String[] args) {
        // 本地时间:2013-07-20 19:30
        LocalDateTime localDate = LocalDateTime.of(2013, Month.JULY, 20, 19, 30);
        ZoneOffset offset = ZoneOffset.of("-08:00");

        OffsetDateTime offsetDate = OffsetDateTime.of(localDate, offset);

        // 找到当月最后一个星期四
        OffsetDateTime lastThursday =
            offsetDate.with(TemporalAdjusters.lastInMonth(DayOfWeek.THURSDAY));

        System.out.printf("The last Thursday in July 2013 is the %sth.%n",
                          lastThursday.getDayOfMonth());
    }
}

输出:

The last Thursday in July 2013 is the 25th.

🎯 场景:日志里的时间戳(2025-09-15T09:30:00+08:00),API 返回值。


4. OffsetTime ⏰ —— 带偏移量的时间(不含日期)

OffsetTime 本质上是: LocalTime + ZoneOffset

  • 只关心一天中的时间点,不关心具体日期。
  • 例子:每天早上 9 点(UTC+02:00)的提醒
import java.time.*;

public class OffsetTimeExample {
    public static void main(String[] args) {
        OffsetTime meeting = OffsetTime.of(9, 0, 0, 0, ZoneOffset.of("+02:00"));
        System.out.println("Meeting time: " + meeting);
    }
}

输出:

Meeting time: 09:00+02:00

🎯 场景:国际会议的每日时间提醒、只关心“几点几分”的任务。


5. 总结 📝

类名组成典型场景
ZonedDateTimeLocalDateTime + ZoneId跨国航班、夏令时转换
OffsetDateTimeLocalDateTime + ZoneOffset日志存储、数据库时间戳、API
OffsetTimeLocalTime + ZoneOffset国际会议提醒、每日任务时间

6. 课堂互动 💡

可以抛几个问题:

  1. 如果我想保存数据库里的交易时间戳,你会选 ZonedDateTime 还是 OffsetDateTime?为什么?
  2. 如果我只关心“每天 8:00+05:30 叫醒服务”,用哪个类更合适?