LocalDateTime类对象中不包含时区信息,因此它不能精确的表示一个时间点。为解决这个问题,Java8新日期时间系统中定义了两个包含时区信息的日期时间类,这两个类分别是ZonedDateTime与OffsetDateTime。虽然这两个类都包含时区信息,但这两个类有一点的区别:ZonedDateTime类对象中包含时差的信息,也有可能包含城市或地区的信息,而OffsetDateTime类中只包含时差的信息,不包含城市或地区的信息。之所以说ZonedDateTime类对象中“有可能”包含城市或地区的信息,是因为ZonedDateTime类中定义了一个ZoneId类型的属性,该属性如果由一个ZoneRegion类对象完成初始化,那么ZonedDateTime类对象中就会包含城市或地区的信息,如果该属性由ZoneOffset类对象完成初始化,则ZonedDateTime类对象中就不包含城市或地区的信息。
10.7.1 对象的创建
通常情况下,创建ZonedDateTime和OffsetDateTime类的对象也是通过now()和of()这两个方法完成。通过now()方法所创建的出对象表示系统当前的日期时间。而如果通过of()方法创建对象,需要给of()方法传递3部分信息,分别是:日期、时间以及时区,日期和时间总共有3种方法来表示:
- 用LocalDateTime类对象表示
- 用一个LocalDate类对象和一个LocalTime类对象组合表示
- 用年、月、日、时、分、秒以及纳秒这些时间分量的数值组合表示
由于日期时间有3种表示方法,对应的of()方法也有3个不同的版本。ZoneDateTime类的of()方法中表示时区的参数是ZoneId类型,因此用ZoneRegion类对象和ZoneOffset类对象都可以充当这个参数,而OffsetDateTime类的of()方法表示时区的参数类型是ZoneOffset,因此仅能用该类型的对象充当参数。下面的【例10_28】展示了使用of()方法创建对象的过程。
【例10_28 创建ZonedDateTime和OffsetDateTime类对象1】
Exam10_28.java
import java.time.*;
public class Exam10_28 {
public static void main(String[] args){
LocalDate ld = LocalDate.of(2022,5,18);
LocalTime lt = LocalTime.of(18,30,25);
LocalDateTime ldt = LocalDateTime.of(ld,lt);
ZoneOffset offset = ZoneOffset.of("+08:00:00");
ZoneId zid = ZoneId.of("Asia/Tokyo");
ZonedDateTime zdt1 = ZonedDateTime.of(ldt,zid);//①
ZonedDateTime zdt2 = ZonedDateTime.of(ld,lt,offset);//②
ZonedDateTime zdt3 = ZonedDateTime.of(2022,5,18,18,30,25,0,zid);//③
OffsetDateTime odt1 = OffsetDateTime.of(ldt,offset);
OffsetDateTime odt2 = OffsetDateTime.of(ld,lt,offset);
OffsetDateTime odt3 = OffsetDateTime.of(2022,5,18,18,30,25,0,offset);
System.out.println("zdt1:"+zdt1);
System.out.println("zdt2:"+zdt2);
System.out.println("odt1:"+odt1);
}
}
【例10_28】中用3种方式创建了3个ZonedDateTime和OffsetDateTime类的对象,并且打印了其中的3个对象。【例10_28】的运行结果如图10-25所示。
图10-25【例10_28】运行结果
由于创建zdt1时,以ZoneRegion类对象作为of()方法的参数,所以zdt1被输出到控制台上后能够看到城市或地区的信息,而创建zdt2时,以ZoneOffset类对象作为of()方法的参数,所以在zdt2中只能看到时差,看不到城市或地区的信息。
假设在一个电商系统中,某一笔交易的完成时间为2022年5月18日早上8点30分。如果这个电商系统使用的是北京时间,那么这个“2022年5月18日早上8点30分”就是指北京时间,如果把这个时间创建为一个对象,并且希望把这个对象表示成日本东京时间,那么就可以用ZoneDateTime类的ofInstant()方法来创建对象。下面的【例10_29】展示了这种特殊的创建对象方式。
【例10_29 创建ZonedDateTime和OffsetDateTime类对象2】
Exam10_29.java
import java.time.*;
public class Exam10_29 {
public static void main(String[] args){
LocalDateTime ld = LocalDateTime.of(2022,5,18,8,30,0);
ZoneOffset offset = ZoneOffset.of("+08:00:00");
ZoneId zid = ZoneId.of("Asia/Tokyo");
ZonedDateTime zdt = ZonedDateTime.ofInstant(ld,offset,zid);
System.out.println("原始日期时间:"+ld);
System.out.println("表示为日本东京时间:"+zdt);
}
}
在【例10_29】中,ld对象表示2022年5月18日早上8点30分,offset表示与零时区的时差为8小时,北京时间与零时区的时间正好相差8小时,所以把ld和offset组合在一起就能表示北京时间的2022年5月18日早上8点30分。zid表示日本东京,把它作为ofInstant()方法的第三个参数就表示通过该方法所创建出的ZonedDateTime类对象要表示成日本东京的日期时间。【例10_29】的运行结果如图10-26所示。
图10-26【例10_29】运行结果
从图10-26可以看出:zdt最终被创建为一个日本东京的日期时间,并且这个日期时间比原始的北京时间早了1个小时,这说明这个日期时间是从原始的北京时间换算而来的。需要提醒读者:ofInstant()方法的第二个参数的类型是ZoneOffset,因此不能以城市或地区来表示时区。
ofInstant()方法还有另外一个版本,这个版本中以Instant类的对象来表示日期时间。Instant类的对象在表示日期时间时只采用格林尼治时间,因此不需要额外指出这个日期时间属于哪一个时区。程序员可以调用Instant类的ofEpochSecond()方法以及ofEpochMilli()方法来创建Instant类对象,这两个方法的参数分别表示距离“时间原点”的秒数和毫秒数。而OffsetDateTime类中仅定义了1个ofInstant()方法,这个方法的日期时间参数的类型也是Instant。下面的【例10_30】展示了如何以Instant类对象作为日期时间去调用ofInstant()方法。
【例10_30创建ZonedDateTime和OffsetDateTime类对象3】
Exam10_30.java
import java.time.*;
public class Exam10_30 {
public static void main(String[] args){
Instant it = Instant.ofEpochSecond(888888888);
ZoneId zid = ZoneId.of("Asia/Shanghai");
ZonedDateTime zdt = ZonedDateTime.ofInstant(it,zid);
OffsetDateTime odt = OffsetDateTime.ofInstant(it,zid);
System.out.println("it所表示的日期时间:"+it);
System.out.println("zdt所表示的日期时间:"+zdt);
System.out.println("odt所表示的日期时间:"+odt);
}
}
【例10_30】创建了一个以Instant类对象所表示的日期时间it,这个日期时间表现为是一个格林尼治时间,之后创建了一个ZonedDateTime对象zdt和一个OffsetDateTime类对象odt,它们与it对象所表示的是同一个时间,但它们都被指定为北京时间。【例10_30】的运行结果如图10-27所示。
图10-27【例10_30】的运行结果
从图10-27可以看出:Instant对象被输出到控制上时以大写字母Z结尾,这个Z就表示零时区。zdt和odt这两个对象与it对象代表了同一时间,it的小时数值为“01”,而zdt和odt所在的时区与零时区有8小时的时差,所以它们的小时数值为“09”。
10.7.2 日期时间的转换与比较
程序中经常需要把某一时区的时间转换为另外一个时区的时间,例如把北京时间的2022年1月1日早上8点30分转换为法国巴黎时间,这样的转换由ZonedDateTime类的withZoneSameInstant()方法完成。此外,程序中有时还需要在保持日期时间数值不变的情况下,把ZonedDateTime类对象中的时区替换为另一个时区,这样的操作则需要调用withZoneSameLocal()方法完成。OffsetDateTime类中也定义了功能相同的两个方法,它们分别是withOffsetSameInstant()和withOffsetSameLocal()。下面的【例10_31】展示了时间转换和时区替换操作的实现过程。
【例10_31时间转换与时区替换】
Exam10_31.java
import java.time.*;
public class Exam10_31 {
public static void main(String[] args) {
LocalDateTime ldt = LocalDateTime.of(2022, 1, 1, 8, 30, 0);
ZoneId zid1 = ZoneId.of("Asia/Shanghai");
ZoneOffset offset1 = ZoneOffset.of("+08:00:00");
ZoneId zid2 = ZoneId.of("Europe/Paris");
ZoneOffset offset2 = ZoneOffset.of("+01:00:00");
ZonedDateTime zdt = ZonedDateTime.of(ldt, zid1);
OffsetDateTime odt = OffsetDateTime.of(ldt, offset1);
System.out.println("zdt的日期时间:"+zdt);
System.out.println("zdt被转换为巴黎时间:"+zdt.withZoneSameInstant(zid2));
System.out.println("zdt的时区被替换为巴黎所在时区:"+zdt.withZoneSameLocal(zid2));
System.out.println("odt的日期时间:"+odt);
System.out.println("odt被转换东1区时间:"+odt.withOffsetSameInstant(offset2));
System.out.println("odt的时区被替换为东1区:"+odt.withOffsetSameLocal(offset2));
}
}
【例10_31】的运行结果如图10-28所示。
图10-28【例10_31】运行结果
从图10-28可以看出:withZoneSameInstant()和withOffsetSameInstant()这两个方法能够把一个时区的时间转换成另一个时区的时间,而withZoneSameLocal()和withOffsetSameLocal()方法仅能把对象中的时区替换为另一个时区,因此替换完成后对象的日期时间数值并没有发生改变。
程序中有时需要对不同时区的时间进行判断和比较,如果判断两个时区不同的时间是否是同一时间,需要用isEqual()方法完成,而不能用equals()方法,这是因为equals()方法仅在两个对象的日期、时间和时区都相同的情况下才返回true。此外,isBefore()和isAfter()这两个方法能够判断不同时区时间的早晚关系。如果计算两个不同时区的时间相隔多久,也要用until()方法来完成。下面的【例10-32】展示了如何比较两个时区不相同的时间。
【例10-32 日期时间的判断和比较】
Exam10_32.java
import java.time.*;
import java.time.temporal.ChronoUnit;
public class Exam10_32 {
public static void main(String[] args){
LocalDateTime ldt1 = LocalDateTime.of(2022, 1, 1, 8, 30, 0);
LocalDateTime ldt2 = LocalDateTime.of(2022, 1, 1, 1, 30, 0);
LocalDateTime ldt3 = LocalDateTime.of(2022, 1, 1, 9, 25, 0);
ZoneId zid1 = ZoneId.of("Asia/Shanghai");
ZoneId zid2 = ZoneId.of("Europe/Paris");
ZoneId zid3 = ZoneId.of("Asia/Tokyo");
ZonedDateTime zdt1 = ZonedDateTime.of(ldt1, zid1);
ZonedDateTime zdt2 = ZonedDateTime.of(ldt2, zid2);
ZonedDateTime zdt3 = ZonedDateTime.of(ldt3, zid3);
System.out.println("zdt1与zdt2是否是同一时刻:"+zdt1.isEqual(zdt2));
System.out.println("使用equals()方法比较zdt1和zdt2:"+zdt1.equals(zid2));
System.out.println("zdt1比zdt3更早:"+zdt1.isBefore(zdt3));
System.out.println("zdt2比zdt3更晚:"+zdt2.isAfter(zdt3));
System.out.println("zdt3比zdt1早"+zdt3.until(zdt1, ChronoUnit.MINUTES)+"分钟");
}
}
【例10-32】中创建了3个ZonedDateTime类对象zdt1,zdt2和zdt3,它们分别是中国北京、法国巴黎和日本东京3个地区的时间。这3个对象的时区虽然不同,但zdt1和zdt2代表了同一时刻,而zdt3比zdt1和zdt2稍早。程序中对这些日期时间的早晚进行了比较,并计算了zdt3与zdt1之间相差多少分钟。【例10-32】的运行结果如图10-29所示。
图10-29【例10-32】运行结果
从图10-29可以看出:通过isEqual()方法能够判断出zdt1和zdt2是同一时间,但通过equals()方法比较二者的关系时判断结果为false。此外还可以看出:使用until()方法能够计算出两个时区不同的时间相隔多久。【例10-32】中仅对ZonedDateTime类对象做了判断和计算,实际上,使用这些方法也可以对OffsetDateTime类对象完成相同的判断和计算。
本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。