10.4小节所介绍的LocalDateTime类对象可以代表一个时间点,但严格的说,这个对象所代表的时间点并不精确,这是因为对象中并不包含时区信息。例如北京时间的2022年6月1日早上8点和法国巴黎时间的2022年6月1日早上8点并不是同一个时间点。因此仅靠“2022年6月1日早上8点”,人们并不能准确的知道这是哪一个时间点。如果希望精确的描述一个时间点,日期时间对象中就必须包含时区信息。
Java8新日期时间系统中专门定义了表示时区的类,这个类的名称叫做ZoneId。ZoneId是一个抽象类,它有两个子类,分别是ZoneRegion,另一个子类叫做ZoneOffset。为什么要定义ZoneRegion和ZoneOffset两个类来表示时区呢?这是因为人们可以通过两种方式来定义时区。
第一种方式是通过城市或地区来定义时区。世界上有很多城市和地区,这些城市或地区都处在某个特定的时区当中,因此如果指定了一个城市或地区,就能确定它所在的时区。ZoneRegion就是代表城市或地区的类,当一个ZoneRegion类对象被创建后,ZoneRegion类对象所代表的那个城市或地区的时区也会被确定,所以说一个ZoneRegion类对象就代表一个时区。
第二种定义时区的方式是指定时差。我们知道:全球共分为24个时区,人们把英国格林尼治时间天文台所在的那个时区称为“零时区”或“中时区”,其他各个时区与零时区都有时差。因此只需要指出与零时区的时差,也能确定一个时区。ZoneOffset就是代表时差的类,但必须说明:ZoneOffset类对象所代表的时差并不是任意两个地区的时差,而是特指与零时区的时差。ZoneOffset类对象中包含了与零时区的时差数值,所以ZoneOffset类对象也能代表一个时区。
因为ZoneId是一个抽象类,因此程序员可以通过ZoneId类的of()静态方法来创建它的子类对象,如果为of()方法传递的字符串参数代表一个城市或地区,那么of()方法将会创建出一个ZoneRegion类的对象。此外,还可以调用systemDefault()方法来创建一个表示系统默认地区的ZoneRegion类的对象。下面的【例10_25】展示了如何创建ZoneRegion类对象以及获取对象中所包含的信息。
【例10_25 创建ZoneRegion类对象】
Exam10_25.java
- import java.time.ZoneId;
- public class Exam10_25 {
- public static void main(String[] args){
- ZoneId zid1 = ZoneId.systemDefault();//①
- ZoneId zid2 = ZoneId.of("Europe/Paris");//②
- System.out.println("zid1的类型:"+zid1.getClass().getName());//③
- System.out.println("zid1的值:"+zid1);
- System.out.println("zid1的时区信息:"+zid1.getRules());
- System.out.println("zid2的类型:"+zid2.getClass().getName());//④
- System.out.println("zid2的值:"+zid2);
- System.out.println("zid2的时区信息:"+zid2.getRules());
- }
- }
【例10_25】的语句①创建了一个操作系统默认的时区对象,而语句②则通过字符串参数创建了一个代表法国巴黎所在时区的对象。语句③和语句④调用了getClass()方法获得这两个对象的类型并打印类型的名称。【例10_25】的运行结果如图10-22所示。
图10-22【例10_25】运行结果
从图10-22可以看出:zid1和zid2都是ZoneRegion类对象。此处需要说明:ZoneRegion不是一个public类,所以在程序中无法直接访问这个类,也创建不出这个类型的引用。由于ZoneRegion和ZoneOffset都代表时区,所以程序中一般都统一使用ZoneId类的引用指向这两个类的对象。此外还可以看出:直接打印ZoneRegion类对象只能输出ZoneRegion类对象所代表的城市或地区,而操作系统默认的时区用ZoneRegion类对象表示出来是“Asia/Shanghai”,意为“亚洲/上海”,并不是人们常用的北京。如果希望打印ZoneRegion类对象的时区信息,需要调用getRules()方法。zid1的时区信息为“ZoneRules[currentStandardOffset=+08:00]”,其中“+08:00”表示这个时区比零时区的时间早8个小时。同理,zid2时区信息中的“+01:00”表示法国巴黎所在的时区比零时区早1个小时。
在创建zid2对象时为of()方法所传递的参数为“Europe/Paris”,它表示“欧洲/巴黎”。但是需要注意:并不是任意一个大洲的名称加上城市的名称所组成的字符串都可以成为of()方法的参数,例如“Asia/Beijing”就不可以当作of()方法的参数,如果以这个字符串作为参数,程序在执行of()方法时会抛出异常。所有能够作为of()方法参数的字符串都被存放到一个集合中,程序员可以通过ZoneId类定义的getAvailableZoneIds()静态方法来获得这个集合,遍历这个集合就可以得到所有可以作为of()方法参数的字符串。下面这段代码展示了如何获取集合并通过增强型for循环遍历集合中所有元素。
- for (String str:ZoneId.getAvailableZoneIds())
- {
- System.out.println(str);
- }
前文讲过:如果给ZoneId的of()方法传递的字符串参数代表一个城市或地区,那么of()方法将会创建出一个ZoneRegion类的对象。那么,如果给of()方法传递的字符串参数代表与零时区的时差,of()方法将会创建一个ZoneOffset类的对象。下面的【例20_26】展示了用多种形式的参数创建ZoneOffset类的对象的过程。
【例20_26 创建ZoneOffset类对象1】
Exam10_26.java
- import java.time.ZoneId;
- public class Exam10_26 {
- public static void main(String[] args){
- ZoneId zid1 = ZoneId.of("+08:00");//①
- ZoneId zid2 = ZoneId.of("+8");//②
- ZoneId zid3 = ZoneId.of("+080000");//③
- ZoneId zid4 = ZoneId.of("+08:30:00");//④
- ZoneId zid5 = ZoneId.of("UTC+08:00:00");//⑤
- ZoneId zid6 = ZoneId.of("GMT+08:00:00");//⑥
- System.out.println("zid5的时区信息:"+zid5.getRules());
- System.out.println("zid6的时区信息:"+zid6.getRules());
- }
- }
【例10_26】通过向of()方法传递表示时差的字符串参数创建了6个ZoneOffset类对象。其中语句①为of()方法所传递的参数为“+08:00”,它表示比零时区的时间早8小时0分。参数中“+”表示比零时区的时间更早,如果换成“-”,则表示比零时区的时间更晚。语句②为of()方法所传递的参数为“+8”,这是一种简略的写法,也表示比零时区的时间早8小时。需要注意:只有在省略分钟的情况下才能省略数字8之前的0,也就是说不能把参数写为“+8:00”。语句③为of()方法传递的参数为“080000”,它表示比零时区的时间早8小时0分0秒,实际上,这个参数的标准写法是“08:00:00”,Java语言规定:如果参数中时、分、秒都用两位数来表示,那么它们之间的冒号(:)可以省略。语句④所创建的对象表示比零时区的时间早8小时30分。这说明ZoneOffset对象所代表的时区与零时区的时差并不一定是1小时的整数倍,它可以与零时区相差任意长度的时间,只要这个时差不超过18小时即可。语句⑤和⑥中,字符串参数中出现了“UTC”和“GMT”,它们代表了两种时间系统。“GMT”表示格林尼治时间,它是通过计算地球自转周期所确定的时间系统,而“UTC”表示协调世界时,它是通过计算原子吸收或释放能量时发出电磁波的频率所确定的时间系统。【例10_26】的运行结果如图10-23所示。
图10-23【例10_26】运行结果
从图10-23可以看出:zid5和zid6的时区信息是一样的,这说明在Java8新日期时间系统中GMT时间和UTC时间之间不存在时间差。
程序员除了可以用ZoneId类的of()方法创建出ZoneOffset类的对象,还可以直接通过ZoneOffset类中所定义的of()方法来创建ZoneOffset对象,需要注意:这个of()方法的参数中不能带有“UTC”或“GMT”。除此之外,ZoneOffset类还定义了一系列以of开头的方法用于创建ZoneOffset类的对象,并且还定义了getTotalSeconds()方法用于计算与零时区的时差是多少秒。下面的【例10_27】展示了如何使用ZoneOffset类所定义的方法创建对象,并如何计算这些对象所代表的时区与零时区之间的时差。
【例20_27 创建ZoneOffset类对象2】
Exam10_27.java
- import java.time.ZoneOffset;
- public class Exam10_27 {
- public static void main(String[] args){
- ZoneOffset zo1 = ZoneOffset.of("+08:00");
- ZoneOffset zo2 = ZoneOffset.of("+8");
- ZoneOffset zo3 = ZoneOffset.of("+080000");
- ZoneOffset zo4 = ZoneOffset.of("+08:30:00");
- //设置时差为3600秒(1小时)
- ZoneOffset zo5 = ZoneOffset.ofTotalSeconds(3600);
- //设置时差为3小时
- ZoneOffset zo6 = ZoneOffset.ofHours(3);
- //设置时差为3小时30分
- ZoneOffset zo7 = ZoneOffset.ofHoursMinutes(3,30);
- //设置时差为4小时20分45秒
- ZoneOffset zo8 = ZoneOffset.ofHoursMinutesSeconds(4,20,45);
- int r1 = zo1.getTotalSeconds();//计算zo1与零时区的时差
- int r2 = zo5.getTotalSeconds();//计算zo5与零时区的时差
- System.out.println("zo1与零时区之间相差"+r1+"秒");
- System.out.println("zo5与零时区之间相差"+r2+"秒");
- System.out.println("zo1与zo5之间相差"+(r1-r2)+"秒");
- }
- }
【例10_27】的运行结果如图10-24所示。
图10-24【例10_27】运行结果