今天来说说时区和时间。这个问题在新手刚接触的时候总是搞得似懂非懂,所以这里只是把最容易搞混的地方来捋清楚,不专门针对API进行讲解。
GMT和UTC时间区别与联系
GMT时间
GMT(Greenwich Mean Time),格林威治时间。这是英国的格林威治皇家天文台为了海上霸权的扩张计划,在十七世纪就开始进行天体观测。为了天文观测,选择了穿过伦敦格林威治天文台子午仪中心的一条经线作为零度参考线,这条线,简称格林威治子午线。
它规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午12点。很显然,因为地球每天的自转是不规则的(正在缓慢减速)所以,格林尼治时间的精确度会越来越低,但是依照地球每天的自转而制定的时间非常符合人的认知,所以说很适用。
1884年10月,在美国华盛顿召开了一个国际子午线会议,该会议将格林威治子午线设定为本初子午线,并将格林威治时间作为世界基准时间(UT, Universal Time)。由此也确定了全球24小时自然时区的划分,所有时区都以和GMT 之间的偏移量做为参考。
时区概念
世界基准时间时区定义如下:
从格林威治本初子午线起,经度每向东或者向西间隔15°,就划分一个时区,在这个区域内,大家使用同样的标准时间。全球共分为24个标准时区,相邻时区的时间相差一个小时。
1972年之前,格林威治时间(GMT)一直是世界时间的标准。1972年之后,GMT不再是一个时间标准了。
UTC
显然,无论是GMT时间还是世界基准时间,其实都不是很精确,那么不禁要问,找一个绝对精确的绝对时间不就行了?
很显然,这不现实的。因为人类所能直接感知的时间就是日升日落(地球自转),因为这种符合作息规律才更容易被人接受和使用。想想全世界都用一种基于原子钟的精确时间,有的地方是日上三竿,有的地方是漫漫黑夜,交流起来岂不是更加困难?
因此,为了既要精确,又要不违背实际的天文规律,UTC时间诞生了。
UTC(Coodinated Universal Time),协调世界时,又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。
UTC是现在全球通用的时间标准,全球各地都同意将各自的时间进行同步协调。
UTC 时间是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以秒为单位的国际原子时所综合精算而成:因为地球自转越来越慢,每年都会比前一年多出零点几秒,每隔几年协调世界时组织都会给世界时+1秒,让基于原子钟的世界时和基于天文学(人类感知)的格林威治标准时间相差不至于太大。
所以,UTC与GMT基本上等同,但是如果要算时间差,肯定是用UTC时间更加精确,此外,UTC时间专指格林威治时间,所以全世界其他地方的时间都要加一个偏移量来表示。
UTC时间的表示
如上文所说,UTC时间要带上时区,那么很显然,UTC时间可以表示如下:
第一种:直接带上时区
类型 | 示例 |
---|---|
日期:年 | 1997 |
日期:年月 | 1997-07 |
日期:年月日 | 1997-07-16 |
日期加小时和分钟 | 1997-07-16T19:20+01:00 |
日期加小时、分钟和秒钟 | 1997-07-16T19:20:30+01:00 |
日期加小时、分钟、秒钟和秒钟的小数部分 | 1997-07-16T19:20:30.45+01:00 |
其中:
-
YYYY = 四位数年份
-
MM = 月份,从 01 到 12 的两位数
-
DD = 几号,从 01 到 31 的两位数
-
T = 在日期后时间前的文本值
-
hh = 小时,从 00 到 23 的两位数
-
mm = 分钟,从 00 到 59 的两位数
-
ss = 秒钟,从 00 到 59 的两位数
-
SSS = 一或多位数,表示秒的小数部分
-
后面的+或者-表示时间的偏移量。
第二种,不带时区,但是明确指出这是UTC时间
如果不带时区指示符,那么,末尾应该带上一个“Z”,表示这是UTC时间而非本地时间。比如说:2015-08-03T07:21:33Z
。转换成北京时间就是2015-08-03 15:21:33
。
下面用代码来说明一下:
Instant now = Instant.now();
Instant gmt = now.atOffset(ZoneOffset.UTC).toInstant();
System.out.println(gmt.toString());
复制代码
输出:
2023-04-14T10:26:27.563Z
复制代码
我写这篇文章时,上面输出的时间+8小时就是北京时间。
请注意,现在通用的就是UTC时间,不用考虑别的时间格式。那种末尾带上GMT标识的时间,JDK已经默认不支持,如果硬要输出带GMT标识的时间,自己用SimpleDateFormat转换吧!
而在JDK8以前,正是因为没有把GMT和UTC区分的很好,导致Java时区一直非常混乱……所以,请把GMT扔进垃圾堆!
ISO其实规定了很多的时间格式,Java的DateTimeFormatter规定如下的合法格式:
常量 | 说明 | 示例 |
---|---|---|
BASIC_ISO_DATE | Basic ISO date | '20111203' |
ISO_LOCAL_DATE | ISO Local Date | '2011-12-03' |
ISO_OFFSET_DATE | ISO Date with offset | '2011-12-03+01:00' |
ISO_DATE | ISO Date with or without offset | '2011-12-03+01:00'; '2011-12-03' |
ISO_LOCAL_TIME | Time without offset | '10:15:30' |
ISO_OFFSET_TIME | Time with offset | '10:15:30+01:00' |
ISO_TIME | Time with or without offset | '10:15:30+01:00'; '10:15:30' |
ISO_LOCAL_DATE_TIME | ISO Local Date and Time | '2011-12-03T10:15:30' |
ISO_OFFSET_DATE_TIME | Date Time with Offset | '2011-12-03T10:15:30+01:00' |
ISO_ZONED_DATE_TIME | Zoned Date Time | '2011-12-03T10:15:30+01:00[Europe/Paris]' |
ISO_DATE_TIME | Date and time with ZoneId | '2011-12-03T10:15:30+01:00[Europe/Paris]' |
ISO_ORDINAL_DATE | Year and day of year | '2012-337' |
ISO_WEEK_DATE | Year and Week | '2012-W48-6' |
ISO_INSTANT | Date and Time of an Instant | '2011-12-03T10:15:30Z' |
RFC_1123_DATE_TIME | RFC 1123 / RFC 822 | 'Tue, 3 Jun 2008 11:05:30 GMT' |
2.关于时区&偏移量
在JDK 8之前,Java使用java.util.TimeZone来表示时区。而在JDK8里分别使用了ZoneId表示时区,ZoneOffset表示UTC的偏移量。
值得强调的是,时区和偏移量在概念和实际作用上是有较大区别的,主要体现在:
-
UTC偏移量仅仅记录了偏移的小时分钟而已,除此之外无任何其它信息。举个例子:+08:00的意思是比UTC时间早8小时,没有地理/时区含义,相应的-03:30代表的意思仅仅是比UTC时间晚3个半小时
-
时区是特定于地区而言的,它和地理上的地区(包括规则)强绑定在一起。比如整个中国都叫东八区,纽约在西五区等等
中国没有夏令时,所有东八区对应的偏移量永远是+8;纽约有夏令时,因此它的偏移量可能是-4也可能是-5。
综合来看,时区更好用。令人恼火的夏令时问题,若你使用UTC偏移量去表示那么就很麻烦,因为它可变:一年内的某些时期在原来基础上偏移量 +1,某些时期 -1;但若你使用ZoneId时区去表示就很方便喽,比如纽约是西五区,你在任何时候获取其当地时间都是能得到正确答案的,因为它内置了对夏令时规则的处理,也就是说啥时候+1啥时候-1时区自己门清,不需要API调用者关心。
如果将时区写死成偏移量,在没有时区规则(没有夏令时)的国家不会存在问题,东八区和UTC+08:00效果永远一样。但在一些夏令时国家(如美国、法国等等),就只能根据时区去获取当地时间,所以当你不了解当地规则时,最好是使用时区而非偏移量。
ZoneId
它代表一个时区的ID,如Europe/Paris
。它规定了一些规则可用于将一个Instant时间戳转换为本地日期/时间LocalDateTime。
上面说了时区ZoneId是包含有规则的,实际上描述偏移量何时以及如何变化的实际规则由java.time.zone.ZoneRules定义。ZoneId则只是一个用于获取底层规则的ID。之所以采用这种方法,是因为规则是由政府定义的,并且经常变化,而ID是稳定的。
可以在这里查看标准时区ID:标准时区ID
因此,要用ZoneId来作为时区。不能单纯用偏移量。
关于Unix时间戳与Instant
Unix时间(Unix Time),是计算机世界的时间,也叫做POSIX时间或纪元时间(Epoch Time),是用来记录时间的流逝,所以也常被叫做时间戳。
定义为从1970-01-01T00:00:00
开始流逝的秒数,不考虑闰秒。之后的时间是正数,之前的是负数。
从定义可以看到,它只代表了从Unix纪元开始流逝的秒数,所以你身处地球上何处,这个时间都是一样的。
一般Unix时间都是精确到秒,但也有些地方Unix时间是精确到毫秒的(比如MySQL 5.6.4之后开始支持到微秒)
Unix时间不是凭空产生的,他就是UTC时间在计算机世界的表达。
Unix时间戳是从1970年1月1日(UTC+00)开始所经过的秒数,不考虑闰秒。时间戳表示的时间是准确、恒定的。就连时间+日期+时区也不行——时区不是恒定不变的,这是一个全球统一的绝对时间。
因此,如果说我们不想对时区进行各种转换,那么最好是存储Unix时间戳,再转换成对应的UTC带时区时间,或者直接存储UTC时间。
Instant
Instant类返回的值计算从 1970 年 1 月 1 日(1970-01-01T00:00:00Z)第一秒开始的时间(精确到纳秒),也称为 EPOCH。 发生在时期之前的瞬间具有负值,并且发生在时期后的瞬间具有正值,因此这个时间就等于UTC时间。
String output = instant.toString();
复制代码
输出:
2023-02-20 T19:15:25.864Z
复制代码
Instant简单理解为就是用一个对象来表示unix时间戳。时间等于当前的UTC时间