ZonedDateTime 是用于表示带时区的日期时间的类,位于 Java 8 的 java.time 包中,它整合了 LocalDateTime(本地日期时间)和 ZoneId(时区)信息,可精确到纳秒,且是不可变、线程安全的。
ZonedDateTime 主要特点:
包含完整的日期(年、月、日)、时间(时、分、秒、纳秒)和时区(ZoneId)信息。
解决了时区转换、夏令时调整等复杂时间问题(相比 LocalDateTime 仅表示本地时间,无时区概念)。
所有修改方法(如增减时间)都会返回新实例,原实例不变(不可变性)。
用于创建 ZonedDateTime 对象,常用方法如下:
根据系统默认时区(ZoneId.systemDefault()),获取当前的日期时间(包含时区信息)。系统默认时区通常由运行环境决定,如操作系统的时区设置。例如:
package com.hxstrive.java_date_time.zonedDateTime; import java.time.ZonedDateTime; public class ZonedDateTimeExample { public static void main(String[] args) { ZonedDateTime now = ZonedDateTime.now(); System.out.println("当前默认时区时间:" + now); } }
运行结果:
当前默认时区时间:2025-09-21T11:18:32.685785300+08:00[Asia/Shanghai]
上述示例,输出结果中包含了完整的日期时间(2025-09-21T11:18:32.685785300)、时区偏移量(+08:00,即 UTC+8)和时区 ID([Asia/Shanghai])。
根据传入的 ZoneId(时区 ID),获取该时区下的当前日期时间。时区 ZoneId 可以通过 ZoneId.of(String zoneId) 方法创建,常用时区 ID 格式为区域/城市(如 Asia/Shanghai、Europe/London、America/New_York)。例如:
package com.hxstrive.java_date_time.zonedDateTime; import java.time.ZonedDateTime; import java.time.ZoneId; public class ZonedDateTimeExample2 { public static void main(String[] args) { // 指定时区:纽约(西五区) ZoneId newYorkZone = ZoneId.of("America/New_York"); ZonedDateTime newYorkTime = ZonedDateTime.now(newYorkZone); System.out.println("纽约当前时间:" + newYorkTime); // 指定时区:伦敦(零时区) ZoneId londonZone = ZoneId.of("Europe/London"); ZonedDateTime londonTime = ZonedDateTime.now(londonZone); System.out.println("伦敦当前时间:" + londonTime); } }
运行结果:
纽约当前时间:2025-09-20T23:21:59.837686200-04:00[America/New_York] 伦敦当前时间:2025-09-21T04:21:59.861041500+01:00[Europe/London]
注意,不同时区的 now(ZoneId) 会返回对应时区的当前时间,偏移量会根据时区和是否为夏令时自动调整(如纽约夏令时为 UTC-4,冬令时为 UTC-5)。
手动指定年、月、日、时、分、秒、纳秒和时区,创建一个精确的 ZonedDateTime 对象。of() 方法有多个重载版本,最常用的是指定完整的日期时间字段,定义如下:
ZonedDateTime.of( int year, // 年份(如2023) int month, // 月份(1-12,1=1月,12=12月) int dayOfMonth, // 日(1-31,需符合当月天数) int hour, // 时(0-23) int minute, // 分(0-59) int second, // 秒(0-59) int nanoOfSecond, // 纳秒(0-999,999,999) ZoneId zone // 时区 )
注意,必须确保所有参数符合实际历法(如月份不能为 0 或 13,日期不能超过当月最大天数),否则会抛出 DateTimeException。
示例代码:
package com.hxstrive.java_date_time.zonedDateTime; import java.time.ZonedDateTime; import java.time.ZoneId; public class ZonedDateTimeExample3 { public static void main(String[] args) { // 创建上海时区的2023年10月1日12:00:00 ZonedDateTime shanghaiTime = ZonedDateTime.of( 2023, 10, 1, // 年月日 12, 0, 0, 0, // 时分秒纳秒 ZoneId.of("Asia/Shanghai") // 时区 ); System.out.println("上海指定时间:" + shanghaiTime); // 创建纽约时区的2023年12月25日8:30:00 ZonedDateTime newYorkXmas = ZonedDateTime.of( 2023, 12, 25, 8, 30, 0, 0, ZoneId.of("America/New_York") ); System.out.println("纽约圣诞时间:" + newYorkXmas); } }
运行结果:
上海指定时间:2023-10-01T12:00+08:00[Asia/Shanghai] 纽约圣诞时间:2023-12-25T08:30-05:00[America/New_York]
注意,of() 方法适合需要精确控制日期时间的场景(如预约时间、历史事件时间等),但需严格保证参数合法(例如 2 月不能传入 30 日)。
用于获取 ZonedDateTime 中的日期、时间或时区信息:
getYear() 获取 ZonedDateTime 中的年份(四位数)。例如,若 zdt 表示 2024 年 8 月 28 日的时间,则 zdt.getYear() 返回 2024。
getMonthValue() 获取月份的数值(1-12,1 代表 1 月,12 代表 12 月)。例如,若 zdt 表示 8 月的时间,则 zdt.getMonthValue() 返回 8。
getDayOfMonth() 获取当月的日期(1-31,具体范围取决于月份)。例如,若 zdt 表示 8 月 28 日,则 zdt.getDayOfMonth() 返回 28。
getHour() 获取小时数(24 小时制,0-23,0 代表午夜,23 代表晚上 11 点)。例如,若 zdt 表示下午 3 点 30 分,则 zdt.getHour() 返回 15。
getMinute() 获取分钟数(0-59)。例如,若 zdt 表示 15:30,则 zdt.getMinute() 返回30。
getZone() 获取 ZonedDateTime 对象关联的时区(ZoneId 对象)。例如,若 zdt 是上海时区的时间,则 zdt.getZone() 返回 ZoneId.of("Asia/Shanghai")。
toLocalDateTime() 将 ZonedDateTime 转换为 LocalDateTime 对象,忽略时区信息,仅保留日期和时间部分。当需要单独处理本地日期时间(不关心时区)时使用。例如,若 zdt 为 2024-08-28T15:30+08:00[Asia/Shanghai],则 zdt.toLocalDateTime() 返回 2024-08-28T15:30。
toInstant() 将 ZonedDateTime 转换为 Instant 对象(UTC 时间戳),表示从 1970 年 1 月 1 日 00:00:00 UTC 开始的毫秒数(精确到纳秒)。用于跨时区时间比较(不同时区的时间转换为 Instant 后可直接比较先后)、时间戳存储等。例如,若 zdt 为上海时区的 2024-08-28T15:30:00+08:00(即 UTC 时间 2024-08-28T07:30:00),则 zdt.toInstant() 返回对应 UTC 时间的 Instant 对象。
示例代码:
package com.hxstrive.java_date_time.zonedDateTime; import java.time.ZonedDateTime; import java.time.ZoneId; import java.time.LocalDateTime; import java.time.Instant; public class ZonedDateTimeGettersExample4 { public static void main(String[] args) { // 创建一个上海时区的ZonedDateTime对象(2024年8月28日15:30:45) ZonedDateTime zdt = ZonedDateTime.of( 2024, 8, 28, // 年月日 15, 30, 45, 0, // 时分秒纳秒 ZoneId.of("Asia/Shanghai") // 上海时区(UTC+8) ); // 1. 获取日期信息 int year = zdt.getYear(); int month = zdt.getMonthValue(); int day = zdt.getDayOfMonth(); // 2. 获取时间信息 int hour = zdt.getHour(); int minute = zdt.getMinute(); // 3. 获取时区信息 ZoneId zone = zdt.getZone(); // 4. 转换为LocalDateTime(忽略时区) LocalDateTime localDateTime = zdt.toLocalDateTime(); // 5. 转换为Instant(UTC时间戳) Instant instant = zdt.toInstant(); // 打印结果 System.out.println("原始ZonedDateTime: " + zdt); System.out.println("年份: " + year); System.out.println("月份: " + month); System.out.println("日期: " + day); System.out.println("小时: " + hour); System.out.println("分钟: " + minute); System.out.println("时区: " + zone); System.out.println("转换为LocalDateTime: " + localDateTime); System.out.println("转换为Instant(UTC时间): " + instant); } }
运行结果:
原始ZonedDateTime: 2024-08-28T15:30:45+08:00[Asia/Shanghai] 年份: 2024 月份: 8 日期: 28 小时: 15 分钟: 30 时区: Asia/Shanghai 转换为LocalDateTime: 2024-08-28T15:30:45 转换为Instant(UTC时间): 2024-08-28T07:30:45Z
用于增减时间或修改日期时间字段(返回新实例,原实例不变):
在当前 ZonedDateTime 基础上增加指定的年数,返回一个新的 ZonedDateTime 对象(原对象不变,因为日期时间类是不可变的)。参数 years 可为正数或负数,负数表示减少年数。
注意:
(1)若计算结果超过 ZonedDateTime 支持的最大年份(通常是 +999999999)或小于最小年份(通常是 -999999999),会抛出 DateTimeException。
(2)调整年份时会自动处理月份和日期的合法性(例如 2024 年 2 月 29 日加 1 年变为 2025 年 2 月 28 日)。
在当前 ZonedDateTime 基础上减少指定的天数,返回新对象(原对象不变)。参数 days 可为正数或负数,负数表示增加天数,等效于 plusDays()。
注意:天数可以是任意整数(包括 0),计算时会自动处理跨月、跨年的情况。例如从 2024 年 3 月 1 日减少 1 天,会得到 2024 年 2 月 29 日(闰年)。示例:
zdt.minusDays(5); // 表示减少 5 天 zdt.minusDays(-3); // 等效于增加 3 天
将当前 ZonedDateTime 的月份修改为指定值,返回新对象(原对象不变)。参数 month 必须在 1-12 之间(1 代表 1 月,12 代表 12 月)。
注意:若参数超出 1-12 范围,会抛出 DateTimeException。若原日期的“日”在新月份中不存在(例如原时间是 3 月 31 日,修改为 2 月),会自动调整为新月份的最后一天(如 2024 年 2 月 29 日)。
将 ZonedDateTime 的小时数修改为指定值(24 小时制),返回新对象(原对象不变)。参数 hour 必须在 0-23 之间(0 代表午夜,23 代表晚上 11 点)。
注意:若参数超出 0-23 范围,会抛出 DateTimeException。仅修改小时部分,分钟、秒、纳秒及日期、时区保持不变。
示例代码:
package com.hxstrive.java_date_time.zonedDateTime; import java.time.ZonedDateTime; import java.time.ZoneId; public class ZonedDateTimeAdjustExample5 { public static void main(String[] args) { // 创建初始ZonedDateTime:2024年3月15日14:30:00,上海时区(UTC+8) ZonedDateTime original = ZonedDateTime.of( 2024, 3, 15, 14, 30, 0, 0, ZoneId.of("Asia/Shanghai") ); System.out.println("初始时间: " + original); // 1. 增加2年(plusYears) ZonedDateTime after2Years = original.plusYears(2); System.out.println("增加2年后: " + after2Years); // 2. 减少5天(minusDays) ZonedDateTime minus5Days = original.minusDays(5); System.out.println("减少5天后: " + minus5Days); // 3. 减少负3天(等效于增加3天) ZonedDateTime plus3Days = original.minusDays(-3); System.out.println("增加3天后(通过minusDays(-3)): " + plus3Days); // 4. 修改月份为10月(withMonth) ZonedDateTime october = original.withMonth(10); System.out.println("修改为10月后: " + october); // 5. 修改小时为9点(withHour) ZonedDateTime nineOClock = original.withHour(9); System.out.println("修改为9点后: " + nineOClock); // 6. 综合调整:增加1年,减少10天,修改为6月,小时改为18点 ZonedDateTime combined = original .plusYears(1) .minusDays(10) .withMonth(6) .withHour(18); System.out.println("综合调整后: " + combined); } }
运行结果:
初始时间: 2024-03-15T14:30+08:00[Asia/Shanghai] 增加2年后: 2026-03-15T14:30+08:00[Asia/Shanghai] 减少5天后: 2024-03-10T14:30+08:00[Asia/Shanghai] 增加3天后(通过minusDays(-3)): 2024-03-18T14:30+08:00[Asia/Shanghai] 修改为10月后: 2024-10-15T14:30+08:00[Asia/Shanghai] 修改为9点后: 2024-03-15T09:30+08:00[Asia/Shanghai] 综合调整后: 2025-06-05T18:30+08:00[Asia/Shanghai]
用于将ZonedDateTime转换到其他时区:
将当前 ZonedDateTime 转换到指定时区,保持绝对时刻(Instant)不变,即转换前后表示的是同一物理时间点,只是时区不同。
Instant 是基于 UTC 时间戳计算目标时区的本地时间。例如,上海(UTC+8)的 10:00 对应的 UTC 时间是 02:00,转换到纽约(UTC-4,夏令时)时,本地时间为 02:00-4=前一天 22:00。
Instant 适用于需要表示“同一时刻在不同时区的本地时间”(如跨国会议的同一时间点在各地的显示时间)。示例:
// 2024-09-11T10:00:00+08:00 ZonedDateTime original = ZonedDateTime.of( 2024, 9, 11, 10, 0, 0, 0, ZoneId.of("Asia/Shanghai") ); ZonedDateTime target = original.withZoneSameInstant(ZoneId.of("America/New_York")); System.out.println(target); // 2024-09-10T22:00-04:00[America/New_York]
将当前 ZonedDateTime 的本地时间(年月日时分秒)原样复制到目标时区,不保证绝对时刻相同,即转换前后的本地时间数值相同,但可能是不同的物理时间点。
适合需要在不同时区使用相同的本地时间数值,如无论在哪个时区,都在本地时间 10 点执行任务。示例:
// 2024-09-11T10:00:00+08:00 ZonedDateTime original = ZonedDateTime.of( 2024, 9, 11, 10, 0, 0, 0, ZoneId.of("Asia/Shanghai") ); ZonedDateTime target = original.withZoneSameLocal(ZoneId.of("America/New_York")); System.out.println(target); // 2024-09-11T10:00-04:00[America/New_York]
注意:
(1)若目标时区在该本地时间存在夏令时切换等异常(如时间不存在或重复),会自动调整(例如跳过不存在的时间,或选择较早的时间)。
(2) 转换结果的绝对时刻(Instant)会发生变化,因为时区不同。
下面是一个综合示例:
package com.hxstrive.java_date_time.zonedDateTime; import java.time.ZonedDateTime; import java.time.ZoneId; public class ZonedDateTimeZoneConversion { public static void main(String[] args) { // 1. 创建初始时间:上海时区(Asia/Shanghai)的2024-09-11 10:00:00 ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai"); ZonedDateTime shanghaiTime = ZonedDateTime.of( 2024, 9, 11, 10, 0, 0, 0, shanghaiZone ); System.out.println("初始时间(上海): " + shanghaiTime); System.out.println("对应的UTC时刻: " + shanghaiTime.toInstant()); // 打印绝对时刻 // 2. 转换到纽约时区(保持瞬间不变:withZoneSameInstant) ZoneId newYorkZone = ZoneId.of("America/New_York"); ZonedDateTime newYorkSameInstant = shanghaiTime.withZoneSameInstant(newYorkZone); System.out.println("\n纽约同一时刻(withZoneSameInstant): " + newYorkSameInstant); System.out.println("对应的UTC时刻(应与初始相同): " + newYorkSameInstant.toInstant()); // 3. 转换到纽约时区(保持本地时间不变:withZoneSameLocal) ZonedDateTime newYorkSameLocal = shanghaiTime.withZoneSameLocal(newYorkZone); System.out.println("\n纽约相同本地时间(withZoneSameLocal): " + newYorkSameLocal); System.out.println("对应的UTC时刻(与初始不同): " + newYorkSameLocal.toInstant()); // 4. 验证两个纽约时间的时间差 long hoursDiff = java.time.Duration.between( newYorkSameInstant, newYorkSameLocal ).toHours(); System.out.println("\n两个纽约时间的时差(小时): " + hoursDiff); } }
运行结果:
初始时间(上海): 2024-09-11T10:00+08:00[Asia/Shanghai] 对应的UTC时刻: 2024-09-11T02:00:00Z 纽约同一时刻(withZoneSameInstant): 2024-09-10T22:00-04:00[America/New_York] 对应的UTC时刻(应与初始相同): 2024-09-11T02:00:00Z 纽约相同本地时间(withZoneSameLocal): 2024-09-11T10:00-04:00[America/New_York] 对应的UTC时刻(与初始不同): 2024-09-11T14:00:00Z 两个纽约时间的时差(小时): 12 Process finished with exit code 0
用于比较两个ZonedDateTime的先后:
判断当前 ZonedDateTime 对象表示的时间是否在参数 other 表示的时间之前。无论两个对象的时区是否相同,都会先转换为同一时间基准(本质是比较对应的 Instant 时间戳),再判断先后。
若当前时间在参数时间之前返回 true,否则返回 false。
判断当前 ZonedDateTime 对象表示的时间是否在参数 other 表示的时间之后。与 isBefore() 一致,基于实际时间戳 Instant 比较,不受时区显示差异影响。
若当前时间在参数时间之后返回 true,否则返回 false。
判断两个 ZonedDateTime 对象是否表示同一时刻(忽略时区的显示差异)。即使两个对象的时区不同,只要它们对应的 UTC 时间戳(Instant)相同,就会返回 true。
示例代码:
import java.time.ZonedDateTime; import java.time.ZoneId; public class ZonedDateTimeComparison { public static void main(String[] args) { // 创建三个不同时区的时间对象 // 1. 上海时间:2024-09-11 10:00:00(UTC+8) ZonedDateTime shanghaiTime = ZonedDateTime.of( 2024, 9, 11, 10, 0, 0, 0, ZoneId.of("Asia/Shanghai") ); // 2. 纽约时间:2024-09-10 22:00:00(UTC-4,夏令时) ZonedDateTime newYorkTime = ZonedDateTime.of( 2024, 9, 10, 22, 0, 0, 0, ZoneId.of("America/New_York") ); // 3. 伦敦时间:2024-09-11 02:00:00(UTC+1,夏令时) ZonedDateTime londonTime = ZonedDateTime.of( 2024, 9, 11, 2, 0, 0, 0, ZoneId.of("Europe/London") ); // 打印各时间的原始值和对应的UTC时间 System.out.println("上海时间: " + shanghaiTime + "(UTC时间: " + shanghaiTime.toInstant() + ")"); System.out.println("纽约时间: " + newYorkTime + "(UTC时间: " + newYorkTime.toInstant() + ")"); System.out.println("伦敦时间: " + londonTime + "(UTC时间: " + londonTime.toInstant() + ")\n"); // 比较上海时间和纽约时间 System.out.println("上海时间是否在纽约时间之前?" + shanghaiTime.isBefore(newYorkTime)); System.out.println("上海时间是否在纽约时间之后?" + shanghaiTime.isAfter(newYorkTime)); System.out.println("上海时间与纽约时间是否为同一时刻?" + shanghaiTime.isEqual(newYorkTime) + "\n"); // 比较上海时间和伦敦时间 System.out.println("上海时间是否在伦敦时间之前?" + shanghaiTime.isBefore(londonTime)); System.out.println("上海时间是否在伦敦时间之后?" + shanghaiTime.isAfter(londonTime)); System.out.println("上海时间与伦敦时间是否为同一时刻?" + shanghaiTime.isEqual(londonTime)); } }
运行结果:
上海时间: 2024-09-11T10:00+08:00[Asia/Shanghai](UTC时间: 2024-09-11T02:00:00Z) 纽约时间: 2024-09-10T22:00-04:00[America/New_York](UTC时间: 2024-09-11T02:00:00Z) 伦敦时间: 2024-09-11T02:00+01:00[Europe/London](UTC时间: 2024-09-11T01:00:00Z) 上海时间是否在纽约时间之前?false 上海时间是否在纽约时间之后?false 上海时间与纽约时间是否为同一时刻?true 上海时间是否在伦敦时间之前?false 上海时间是否在伦敦时间之后?true 上海时间与伦敦时间是否为同一时刻?false Process finished with exit code 0
总之,ZonedDateTime 类提供了简洁、安全的时区时间处理方案,避免了旧 API(如 Date、Calendar)的线程不安全和设计缺陷。