Bootstrap

高级JAVA开发必备技能:java8 新日期时间API((一)JSR-310:ZoneId 时区和偏移量)(JAVA 小虚竹

协调世界时跟地区位置没有相关,不代表当前时刻某个地方的时间,所以在说某个地方时间时要加上时区。例如:中国就是UTC+8。

UTC是时间标准,这个标准把世界分成UTC-12到UTC+12共24个时区。

GMT

GMT(Greenwich Mean Time)别名:格林尼治时间(有时候翻译也叫格林威治),中文名:世界时。

GMT是指格林尼治所在地的标准时间,也是表示地球自转速率的一种形式。以地球自转为基础的时间计量系统。地球自转的角度可用地方子午线相对于地球上的基本参考点的运动来度量。为了测量地球自转,人们在地球上选取了两个基本参考点:春分点(见分至点)和平太阳点,由此确定的时间分别称为恒星时和平太阳时。
–引用自百度百科

GMT并不等于UTC,只是格林尼治刚好在0时区上。所以GMT = UTC+0才是对的。

CST

CST可视为美国、澳大利亚、古巴或中国的标准时间
美国中部时间:Central Standard Time (USA) UT-6:00
澳大利亚中部时间:Central Standard Time (Australia) UT+9:30
中国标准时间:China Standard Time UT+8:00
古巴标准时间:Cuba Standard Time UT-4:00

–引用自百度百科

所以在换算CST时间时,要注意对应的时区。这是一个坑。

美国中部时间:CST=UTC/GMT-6;

中国标准时间:CST=UTC/GMT+8;

DST

DST(Daylight Saving Time)中文名:夏令时。

表示为了节约能源,人为规定时间的意思。也叫夏时制,夏令时(Daylight Saving Time:DST),又称“日光节约时制”和“夏令时间”,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。全世界有近110个国家每年要实行夏令时。
–引用自百度百科

中国实现DST时间范围:1986年至1991年。

ISO-8601

国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前最新为第三版ISO8601:2004,第一版为ISO8601:1988,第二版为ISO8601:2000
–引用自百度百科

年由4位数字组成YYYY,或者带正负号的四或五位数字表示±YYYYY。以公历公元1年为0001年,以公元前1年为0000年,公元前2年为-0001年。

月、日用两位数字表示:MM、DD。

只使用数字为基本格式。使用短横线"-"间隔开年、月、日为扩展格式。

小时、分和秒都用2位数表示,对UTC时间最后加一个大写字母Z,其他时区用实际时间加时差表示。如UTC时间下午2点30分5秒表示为14:30:05Z或143005Z,当时的北京时间表示为22:30:05+08:00或223005+0800,也可以简化成223005+08。

注:大家还记得java的Date类吗?它默认就是使用ISO-8601表示的。

第二节:JDK8之前:时区/偏移量TimeZone

在JDK8之前,我们一直用java.util.TimeZone来表示和处理时区和偏移量。

TimeZone.getDefault() 获得当前JVM所运行的时区,那它是怎么获取默认时区的呢,之前有写过分析文章,有兴趣的可以了解下,这里就不再重复了。

JDK获取默认时区的风险和最佳实践

有时候需要做时区的时间转换,比如一个时间要用北京时间和纽约时间显示。实现:

这里没有到SimpleDateFormat 来格式化时间是因为它是线程不安全的。选用线程安全的FastDateFormat,

Apache Commons Lang包支持。

有兴趣可以了解下FastDateFormat 的源码分析:java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案

		String patternStr = "yyyy-MM-dd HH:mm:ss";
		// 北京时间(new出来就是默认时区的时间)
		Date bjDate = new Date();
		// 得到纽约的时区
		TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New\_York");
		// 根据此时区 将北京时间转换为纽约的Date
		FastDateFormat fastDateFormat = FastDateFormat.getInstance(patternStr,newYorkTimeZone);
		System.out.println("这是北京时间:" + FastDateFormat.getInstance(patternStr).format(bjDate));
		System.out.println("这是纽约时间:" + fastDateFormat.format(bjDate));

image-202108109417345

19-7=12 北京时间比纽约时间快12小时。

image-202108101999961

第三节:JDK8开始支持:时区/偏移量 ZoneId/ZoneOffset

JDK8中ZoneId表示时区的ID,ZoneOffset表示Greenwich/UTC的偏移量。

image-2021081095044291

ZoneId 是用来替换java.util.TimeZone 的。

我们来研究下ZoneId ,ZoneId代表一个时区的ID,它是确定的。但是时区ID是有对应的规则,规则变化为java.time.zone.ZoneRules 决定。像夏令时规则是由各国政府定的,可能会变化,不同的年还不一样,这个就交给JDK底层机制来保持同步,我们调用者不需要关心(不!要关心!当技术不再是黑盒时,才能做到心里有底! )。

时区的规则发生变化时,如何同步时区

TZUpdater 工具介绍

​ 提供的 TZUpdater 工具 允许您使用更新的时区数据更新已安装的 Java 开发工具包 (JDK) 和 Java 运行时环境 (JRE) 软件,以适应不同国家/地区的夏令时 (DST) 更改。Oracle 依赖于通过 IANA 的时区数据库公开提供的时区数据。

如果您无法使用 Oracle 最新的 JDK 或 JRE 更新版本,或者如果最新版本上的时区数据不是最新可用的,TZUpdater 工具提供了一种更新时区数据的方法,同时保持其他系统配置和依赖项不变.

TZUpdater 工具用法

TZUpdater 工具用于执行该工具的 JDK/JRE 软件实例。每次执行都会修改 JDK/JRE 软件。要将工具管理到 JDK/JRE 软件的多个实例。

在安装的 JDK/JRE 软件上运行 TZUpdater 工具之前,您必须停止操作系统上的 JDK/JRE 软件的任何正在运行的服务。

使用以下命令运行 TZUpdater 工具:

java -jar tzupdater.jar options

要成功更新时区数据,您应该确保您有足够的权限来修改JDK_HOME /jre/libJRE_HOME /lib目录。

如果未指定任何选项,则会显示用法消息。要更新时区数据,请使用-l-f选项。

选项描述
-h, --help
将用法打印到stdout并退出。如果指定此选项,则其他选项将被忽略。
-V, --version打印工具版本、JRE 中的 tzdata 版本以及工具将更新到的 tzdata 版本,然后退出。
-l, --location url-link-to-archive-file从提供的tzdata.tar.gz包中编译、测试和更新 JRE 时区数据,例如-l https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz. 支持的 URL 协议:http://、https://、file://。如果未提供 URL 链接,该工具将使用位于 的最新 IANA tzdata 包https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz
-f, --force强制 tzdata 更新。如果更新到较旧的 tzdata 版本,请使用此选项。
-v, --verbose向 显示详细消息stdout
手动升级

注意:

1、在安装的 JDK/JRE 软件上运行 TZUpdater 工具之前,您必须停止操作系统上的 JDK/JRE 软件的任何正在运行的服务。

2、要成功更新时区数据,您应该确保您有足够的权限来修改JDK_HOME /jre/libJRE_HOME /lib目录。(linux系统:JRE目录要有写权限;windows系统:用管理员身份运行cmd)

3、如果系统上有多个JDK/JRE ,需要将该工具用于每个JDK/JRE中(每个JDK/JRE都要操作一遍)

4、更新成功后,要重新启动此 JDK/JRE 实例上的应用程序服务(如果还没更新,重启下服务器试试)

操作步骤:

1、下载Oracle官方提供的tzupdater.jar包;下载地址

https://www.oracle.com/java/technologies/javase-tzupdater-downloads.html

把tzupdater.jar放到java目录bin目录下,比如

“C:\Program Files\JAVA\java-1.8.0-openjdk-1.8.0.201\bin\tzupdater.jar”;

image-20210811935505

2、查看当前时区数据库版本,以windows为例,用管理员身份运行cmd,切换到tzupdater.jar对应的目录:

java -jar tzupdater.jar -V

image-20210811933080

3、在线更新,以windows为例,用管理员身份运行cmd,切换到tzupdater.jar对应的目录:(第3种和第4种更新方式任选一种)

java -jar tzupdater.jar -l https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz

image-2021081193461

如图所示,已经更新成功到了tzdata2021a版本了。

更新后的文件是放在jre/lib/tzdb.dat ,如图所示,它有备份历史的版本。

image-2021081198385

4、离线更新:要先下载最新的时区数据,下载地址:

https://data.iana.org/time-zones/releases/

image-202108119194

以windows为例,用管理员身份运行cmd。切换到tzupdater.jar对应的目录:

java -jar tzupdater.jar -l file:///[path]/tzdata.tar.gz 

注:

windows建议放在C盘根目录下,路径目录也不要有中文;

用管理员身份运行cmd(需要写权限);

如上面的命令所示,file后面的/是3个

5、以上执行完后,用第2步的查看当前时区数据库版本命令,查看是否更新成功。

image-2021081193461

服务自动化升级
思路步骤:

1、设置定时任务(操作系统配置就行),执行tzupdater 更新时区的命令脚本;

2、新开一个时区服务,用来对外提供时区和夏令时规则读取服务,独立部署;

3、在时区服务中,写个同步按钮,用来执行tzupdater 更新时区的命令脚本;

4、在时区服务中,将timeZone数据定时写到自定义的时区表中。提供维护功能,可以自定义新增修改删除timeZone数据。

此思路的好处:

1、其他服务不需要停止服务来更新时间,直接通过调用时区服务的数据,可保证获取到最新的时区数据;

2、自动化的好处,避免了手动维护时区的繁琐,人工介入有引发问题的风险;

3、时区服务和其他业务服务是拆分的,方便未来的扩展。

系统默认的ZoneId

	@Test
	public void timeZoneTest2(){
		System.out.println("JDK 8之前做法:"+TimeZone.getDefault());

		System.out.println("JDK 8之后做法:"+ZoneId.systemDefault());
	}

image-202108109107753

ZoneId.systemDefault()方法实现上是调用了TimeZone:
 public static ZoneId systemDefault() {
        return TimeZone.getDefault().toZoneId();
    }

所以两个的结果是一样的(Asia/Shanghai),这个很正常。

TimeZone.toZoneId() 是java8 后加的方法。

   /\*\*
 \* Converts this {@code TimeZone} object to a {@code ZoneId}.
 \*
 \* @return a {@code ZoneId} representing the same time zone as this
 \* {@code TimeZone}
 \* @since 1.8
 \*/
    public ZoneId toZoneId() {
        String id = getID();
        if (ZoneInfoFile.useOldMapping() && id.length() == 3) {
            if ("EST".equals(id))
                return ZoneId.of("America/New\_York");
            if ("MST".equals(id))
                return ZoneId.of("America/Denver");
            if ("HST".equals(id))
                return ZoneId.of("America/Honolulu");
        }
        return ZoneId.of(id, ZoneId.SHORT_IDS);
    }

指定字符串得到ZoneId和获取所有的zoneIds

		System.out.println(ZoneId.of("America/New\_York"));

		System.out.println(ZoneId.of("Asia/Shanghai"));

image-20210810990025341

	@Test
	public void ZoneIdTest2(){
		Set<String> zoneIds = ZoneId.getAvailableZoneIds();
		System.out.println("zoneIds长度:"+zoneIds.size());
		for(String zoneId : zoneIds){
			System.out.println(zoneId);
		}
	}

image-20210810210652005

指定的字符串不能乱写,不然会报错,要在ZoneId.getAvailableZoneIds() 的集合范围里。

从日期中获取时区

System.out.println(ZoneId.from(ZonedDateTime.now()));
			
System.out.println(ZoneId.from(ZoneOffset.of("+8")));

image-202108109411428

从日期中获取时区只支持带有时区的TemporalAccessor ,像LocalDateTime,LocalDate是不可以的,会报错。

		try {
			System.out.println(ZoneId.from(LocalDateTime.now()));
		}catch (Exception e){
			e.printStackTrace();
		}
		try {
			System.out.println(ZoneId.from(LocalDate.now()));
		}catch (Exception e){
			e.printStackTrace();
		}

image-202108109721346

ZoneId是抽象类,它有两个继承实现类:

  • ZoneOffset:时区偏移量
  • ZoneRegion:地理区域

image-20210810927677

ZoneOffset(时区偏移量)

时区偏移量是时区与Greenwich/UTC之间的时间差,一般是固定的小时数和分钟数。

最小/最大偏移量
	@Test
	public void ZoneIdTest5(){
		System.out.println("最小偏移量:" + ZoneOffset.MIN);
		System.out.println("最小偏移量:" + ZoneOffset.MAX);
		System.out.println("中心偏移量:" + ZoneOffset.UTC);
		// 超出最大范围
		System.out.println(ZoneOffset.of("+100"));
	}

image-202108109251152

超出最大范围会报错

时分秒构造偏移量


### 给大家的福利


**零基础入门**


对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。


![](https://img-blog.csdnimg.cn/img_convert/95608e9062782d28f4f04f821405d99a.png)


同时每个成长路线对应的板块都有配套的视频提供:


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a91b9e8100834e9291cfcf1695d8cd42.png#pic_center)


因篇幅有限,仅展示部分资料

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

;