在 Java8 中,ZonedDateTime 类用于表示具有时区信息的日期时间。ZonedDateTime 结合了 LocalDateTime(表示日期和时间,但没有时区)和 ZoneId(表示时区),能够准确地处理不同时区的时间。
时区是为了适应地球自转造成的不同地区日出日落时间的差异而划分的区域。
由于地球自转,太阳在不同经度上的升起和落下时间不同。为了方便统一时间计量和日常生活,人们将地球表面按照经度每 15 度划分为一个时区,全球共分为 24 个时区。
每个时区都有一个标准时间,相邻时区的时间相差 1 小时。例如,当格林威治标准时间(GMT)是中午 12 点时,位于东一区的地区是下午 1 点,位于西一区的地区是上午 11 点。
时区也许是因为它完全是由人类创造的,所以它们比地球不规则自转所造成的复杂情况更加混乱。在一个理性的世界中,我们都遵循格林威治的时钟,于是有些人会在 02:00 吃午餐,而其他人会在 22:00 吃午餐。
尽管时区的变化无常似乎是一种人为的选择,但是实际上,它们就是生活中的事实。当实现一个日历应用程序时,需要让它能够服务于从一个国家飞往另一个国家的人。假设你原本在纽约有一个早上 10:00 的电话会议,但你碰巧在柏林,你一定希望日历程序能够按照正确的柏林本地时间提醒你。
因特网编号管理局 (Internet Assigned Numbers Authority,IANA) 维护着一份全球所有已知时区的数据库 (https://www.iana.org/time-zones),每年会更新几次。这些更新主要处理夏令时规则的改变,Java 就使用了 IANA 的数据库。
每个时区都有一个 ID,例如 America/New_York 或者 Europe/Berlin,点击查看更多。要想获得所有可用的时区,你可以调用 ZoneId.getAvailableIds()。
带有 ISO-8601 日历系统时区的日期时间,例如 2007-12-03T10:15:30+01:00 Europe/Paris。
ZonedDateTime 是带有时区的日期时间的不可变表示。该类存储所有日期和时间字段(精度为纳秒)以及时区,时区偏移用于处理含糊不清的本地日期时间。例如“2nd October 2007 at 13:45.30.123456789 +02:00 in the Europe/Paris time-zone”的值可以存储在 ZonedDateTime 中。
该类处理从 LocalDateTime 的本地时间线到 Instant 的瞬时时间线的转换。两条时间线之间的差异是与 UTC/Greenwich 的偏移量,由 ZoneOffset 表示。
在两条时间线之间进行转换时,需要使用从 ZoneId 获取的规则计算偏移量。获取瞬时的偏移量很简单,因为每个瞬时都有一个有效的偏移量。相比之下,获取本地日期时间的偏移量并不简单。有三种情况:
正常情况,只有一个有效偏移量。在一年中的绝大多数时间里,正常情况都适用,即本地日期时间有一个有效的偏移量。
间隙,零有效偏移。这种情况通常是由于春季夏令时从 "冬季" 变为 "夏季",时钟向前跳动。在间隙中,本地日期时间值没有有效偏移。
重叠,有两个有效偏移。这通常是由于秋季夏令时从 "夏季" 变为 "冬季" 而导致时钟调回。在重叠中,有两个有效偏移的本地日期时间值。
任何通过获取偏移量,直接或隐含地将本地日期时间转换为瞬时的方法,都有可能变得复杂。
对于间隙(Gap),一般的策略是,如果本地日期时间位于间隙的中间,那么得到的分区日期时间的本地日期时间将前移间隙的长度,从而得到较晚偏移的日期时间,通常是 "夏" 时。
对于重叠时间,一般的策略是,如果本地日期时间位于重叠时间的中间,则保留之前的偏移量。如果没有先前的偏移量,或者先前的偏移量无效,则使用较早的偏移量,通常是 "夏 " 时。另外还有两个方法:withEarlierOffsetAtOverlap() 和 withLaterOffsetAtOverlap(),可以帮助管理重叠情况。
在设计方面,该类主要应被视为 LocalDateTime 和 ZoneId 的组合。ZoneOffset 是一个重要但次要的信息,用于确保该类代表一个即时时间,尤其是在夏令时重叠期间。
这是一个基于值的类,在 ZonedDateTime 实例上使用对身份敏感的操作(包括引用相等(==)、身份哈希代码或同步)可能会产生不可预知的结果,因此应避免使用。应使用 equals 方法进行比较。
ZonedDateTime 类的一些常用方法如下:
now():从默认时区中的系统时钟获取当前日期时间。
now(Clock clock):从指定的时钟中获取当前日期时间。
now(ZoneId zone):从指定时区中的系统时钟获得当前日期时间。
of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone):根据指定的年、月、日、小时、分钟、秒、纳秒和时区来创建 ZonedDateTime 对象。
of(LocalDate date, LocalTime time, ZoneId zone):通过给定的本地日期和时间以及时区来创建 ZonedDateTime 对象。
of(LocalDateTime localDateTime, ZoneId zone):从本地日期时间和指定时区创建 ZonedDateTime 对象。
parse(CharSequence text):从一个符合特定格式的文本字符串(如2007-12-03T10:15:30+01:00(Europe/Paris))创建 ZonedDateTime 对象。
parse(CharSequence text, DateTimeFormatter formatter):使用特定格式的文本字符串和相应的 DateTimeFormatter 创建 ZonedDateTime 对象。
withZoneSameInstant(ZoneId zone):返回此日期时间的副本,并转换到不同的时区,同时保留原始的时间点(即时区转换时,日期和时间会相应调整)。
toLocalDate():将 ZonedDateTime 转换为本地日期。
toLocalTime():将 ZonedDateTime 转换为本地时间。
getZone():获取该 ZonedDateTime 对象所在的时区。
使用 now() 静态方法创建 ZonedDateTime 的实例,例如:
package com.hxstrive.jdk8.date_time.zonedDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; /** * ZonedDateTime 类 * @author hxstrive.com */ public class ZonedDateTimeDemo1 { public static void main(String[] args) { // 获取系统默认时区的当前日期和时间 ZonedDateTime now = ZonedDateTime.now(); System.out.println(now); //2024-06-30T17:19:53.943014600+08:00[Asia/Shanghai] // 获取指定时区的当前日期和时间 ZonedDateTime nowWithSpecificZone = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); System.out.println(nowWithSpecificZone); //2024-06-30T17:19:53.943014600+08:00[Asia/Shanghai] } }
使用 LocalDateTime.of() 静态方法创建 LocalDateTime 的实例,然后使用 atZone() 实例方法将 LocalDateTime 转换为 ZonedDateTime 对象。例如:
package com.hxstrive.jdk8.date_time.zonedDateTime; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; /** * ZonedDateTime 类 * @author hxstrive.com */ public class ZonedDateTimeDemo2 { public static void main(String[] args) { // 创建一个LocalDateTime实例 LocalDateTime dateTime = LocalDateTime.of(2023, 3, 15, 10, 30); // 将LocalDateTime转换为ZonedDateTime,指定时区 ZonedDateTime zonedDateTime = dateTime.atZone(ZoneId.of("America/New_York")); System.out.println(zonedDateTime); //2023-03-15T10:30-04:00[America/New_York] } }
使用 ZonedDateTime.of() 方法创建 ZonedDateTime 的实例,然后使用 plus() 和 minus() 方法对 ZonedDateTime 进行增减操作。例如:
package com.hxstrive.jdk8.date_time.zonedDateTime; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; /** * ZonedDateTime 类 * @author hxstrive.com */ public class ZonedDateTimeDemo3 { public static void main(String[] args) { // 创建一个ZonedDateTime实例 ZonedDateTime start = ZonedDateTime.of(2023, 3, 15, 10, 30, 0, 0, ZoneId.of("America/New_York")); // 添加3天 ZonedDateTime end = start.plus(3, ChronoUnit.DAYS); System.out.println("三天后:" + end); //三天后:2023-03-18T10:30-04:00[America/New_York] // 减去2小时 ZonedDateTime earlier = end.minus(2, ChronoUnit.HOURS); System.out.println("2小时前:" + earlier); //2小时前:2023-03-18T08:30-04:00[America/New_York] } }
下面示例使用 DateTimeFormatter 将 ZonedDateTime 转换成一个字符串,例如:
package com.hxstrive.jdk8.date_time.zonedDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; /** * ZonedDateTime 类 * @author hxstrive.com */ public class ZonedDateTimeDemo4 { public static void main(String[] args) { // 创建一个ZonedDateTime实例 ZonedDateTime dateTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); // 创建一个DateTimeFormatter实例 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss zzz"); // 格式化ZonedDateTime String formattedDateTime = dateTime.format(formatter); System.out.println(formattedDateTime); //2024-06-30 17:25:09 CST } }