鉴于每次用到时间的相关方法时,都是通过谷歌搜索如何实现,自己对这些实现背后隐藏的内容知之甚少。所以,这次打算开一篇文章,学习JDK中主要的时间类,了解之前用到时间类、参数和方法代表的含义。
Java8 掌握Date与Java.time转换的核心思路,轻松解决各种时间转换问题
UTC(Coordinated Universal Time,世界标准时,世界协调时)是最主要的世界时间标准。UTC基于国际原子时,并通过不规则的加入闰秒来抵消地球自转变慢的影响。鉴于UTC已经是Java中主要的世界时表示方式,除非特殊提示否则下文中都UTC来表示世界标准时时间。
GMT(Greenwich Mean Time,格林尼治时间),由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,目前已经被原子钟报时的协调世界时(UTC)所取代。
UT(Universal Time,世界时),是一种以格林威治子夜起算的平太阳时。世界时是以地球自转为基准得到的时间尺度,其精度受到地球自转不均匀变化和极移的影响。
Instant(1.8)是jdk8时间框架的基础类,它以世界标准时UTC作为基础时区构建,基本上jdk8中的时间类都会于它打交道,支持以GMT、UT作为基础时区。
TimeZone(1.7前)它以世界标准时GMT作为基础时区构建,不支持UTC、UT时区。
用Instant来直接输出UTC当前的时间:
// Instant.now()代表UTC当前时间的秒 + 纳秒(从1970-1-1 00:00:00 开始所经过秒-纳秒数)
Instant instant = Instant.now(); // UTC(Z) ~ GMT ~ UT
System.out.println(instant);
输出如下时间:
2023-03-02T07:53:02.377Z
地区时:是适用相同时区规则的地理区域,是UTC偏移量位置处的时间。在同一时刻,地区时根据UTC距当地的偏移量来计算其本地时间。
中国占据东五区、东六区、东七区、东八区、东九区5个时区,为了统一时间,1949年中华人民共和国成立后,中国大陆全境统一划为东八区,同时以北京时间作为全国唯一的标准时间。北京时间,又名中国标准时间(China Standard Time,CST,UTC+8),是我国的标准时间,比世界协调时快八小时(UTC+8)。注意CST并不能作为北京时间的时区ID来生成ZoneId,原因见时区简称。
对比一下世界标准时、其它地区时和北京时间的时间差异:
Instant instant = Instant.now();
System.out.println("UTC:" + instant);
System.out.println("纽约 UTC-5:" + instant.atZone(ZoneId.of("America/New_York")));
System.out.println("东京 UTC+9:" + instant.atZone(ZoneId.of("Asia/Tokyo")));
System.out.println("上海 UTC+8:" + instant.atZone(ZoneId.of("Asia/Shanghai")));
输出内容如下:
UTC:2023-03-02T08:37:10.736Z
纽约 UTC-5:2023-03-02T03:37:10.736-05:00[America/New_York]
东京 UTC+9:2023-03-02T17:37:10.736+09:00[Asia/Tokyo]
上海 UTC+8:2023-03-02T16:37:10.736+08:00[Asia/Shanghai]
可以通过这些网站查询各个时区以及主要城市的时区:timeanddate,时区列表
时区:是地球上使用的同一时间定义的国家或区域,它描述了这个国家或区域内的偏移量范围。
- ZoneId代表时区。它的内部描述了这个国家或区域的整体时间规则。 即
ZoneId
=ZoneOffset
+ZoneRules
。- ZoneRules代表这个时区的时区规则细则,包含这个国家或区域的时区偏移量的变动和这个变动的时间,不同的时间这个时区可能会有不同的时间偏移量(ZoneOffset)情况。比如
Asia/Shanghai
,Asia/Singapore
,虽然当前它们都使用的UTC+8
这个时区的区时,但是不代表这些国家或地区的曾经或者未来还会使用这个区时,我国就在1986年至1991年使用过六年夏令时,即每年在4月中旬的第一个星期日2时整(北京时间)到9月中旬第一个星期日的凌晨2时整(北京夏令时),夏令时内时间将会向后调快一小时,使用+9:00时区的区时。ZoneRules中就完整的记录了这些变动。
时区和区时:时区是一个国家或区域,区时是这个国家或区域的本地时间
在Java中,以ZoneId
(1.8)和TimeZone
(1.1)来表示时区概念。
ZoneId:
当前默认时区:
ZoneId zoneId = ZoneId.systemDefault(); // 本质是调用TimeZone.getDefault().toZoneId()
TimeZone timeZone = TimeZone.getDefault();
// 可以设置程序全局的默认时区
TimeZone.setDefault(timeZone);
ZoneId(1.8):
如下程序,展示了通过时区ID获取该时区的本地时间:
System.out.println(ZonedDateTime.now(ZoneId.of("UTC"))); // 2023-03-22T07:22:03.133Z[UTC]
System.out.println(ZonedDateTime.now(ZoneId.of("GMT"))); // 2023-03-22T07:22:03.133Z[GMT]
System.out.println(ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))); // 2023-03-22T15:22:03.133+08:00[Asia/Shanghai]
TimeZone(1.1):
Date类中没有时区概念 可以借助SimpleDateFormat实现:
TimeZone utc = TimeZone.getTimeZone("UTC+8");
TimeZone gmt = TimeZone.getTimeZone("GMT+8");
TimeZone ut = TimeZone.getTimeZone("UT+8");
Date date = new Date(); // 2023-04-14 11:04:17
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(utc);
System.out.println(sdf.format(date)); // 2023-04-14 03:04:17
sdf.setTimeZone(gmt);
System.out.println(sdf.format(date)); // 2023-04-14 11:04:17
sdf.setTimeZone(ut);
System.out.println(sdf.format(date)); // 2023-04-14 03:04:17
上面的程序中,UTC+8、GMT+8、UT+8三个时区并没有输出相同的时间,只有GMT+8正确的输出了当前时间,原因是TimeZone是基于GMT构建的,它并不支持UTC+8、UT+8这两种时间标准时的偏移设置。此外以UTC
作为时区ID时可以被识别成功,UT
则不行。
刚开始用的时候,由于使用UTC与UT没报错,我还以为TimeZone支持这两种世界标准时,但是通过分析源码才发现TimeZone会将所有没有解析成功的时区ID转为GMT。
private static TimeZone getTimeZone(String ID, boolean fallback) {
TimeZone tz = ZoneInfo.getTimeZone(ID); // 时区ID中仅存在UTC,不存在其偏移设置即UTC+1这种格式;不存在UT。
if (tz == null) {
tz = parseCustomTimeZone(ID); // 解析GMT的自定义偏移时间设置,如GMT+14:17
if (tz == null && fallback) {
tz = new ZoneInfo(GMT_ID, 0); // 所有未被识别的时区都会被设置为“GMT+0”
.....
}
TimeZone中可以正确使用的ID
ZoneId与TimeZone兼容性
为了兼容ZoneId带来的改变,1.8版本中为TimeZone类添加了ZoneId与TimeZone互转的方法:
ZoneId zone = ZoneId.of("Asia/Shanghai");
TimeZone timeZone = TimeZone.getTimeZone(zone);
ZoneId toZoneId = timeZone.toZoneId();
查看可用时区
如上所示,通过ZoneId和TimeZone都是通过时区ID来创建对应时区,那么你要怎么知道有哪些时区ID可用呢?
可以通过如下方式查看可用ID:
// ZoneId可用的时区ID
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
// TimeZone可用的时区ID
String[] availableIds = TimeZone.getAvailableIDs();
// 或通过ZoneInfoFile查看时区ID
String[] zoneIds = ZoneInfoFile.getZoneIds();
查看指定时区
如果要查找指定偏移量位置的时区可以通过如下方式实现(单位毫秒):
// 查找位于+8时区的所有时区ID
String[] availableIDs = ZoneInfo.getAvailableIDs(8 * 60 * 60 * 1000);
String[] availableIDs1= TimeZone.getAvailableIDs(8 * 60 * 60 * 1000);
查看时区的历史区时
我国就在1986年至1991年使用过六年夏令时(每年在4月中旬的第一个星期日2时整(北京时间)到9月中旬第一个星期日的凌晨2时整(北京夏令时),时间调快一个小时)。ZoneId由于记录了这些时区的历史变动,所以它可以很轻易的得到我国实行夏令时当时的本地时间:
// 输出曾经的夏令时时间
ZoneId ctt = ZoneId.of("Asia/Shanghai");
Instant parse = Instant.parse("1986-06-01T00:00:00.000Z");
LocalDateTime localDateTime = LocalDateTime.<