先来看看几种常见的日期格式:
2021-03-25 20:00:00
2021-03-25T20:00:00+0800
2021-03-25T20:00:00+08:00
Thu Mar 25 20:00:00 CST 2021
2021-03-25T20:00:00Z
第一种肯定都不陌生,后面的也很常见,但是里面的T
、Z
、CST
、+0800
分别是什么意思呢?
T
很简单,代表时间,国际标准规定的日期时间组合法,用T放在时间内容前,代表Time,而最后的部分,代表时区。
Timezone(时区)
什么是时区?
世界各国位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。
火车铁路与其他交通和通讯工具的发展,以及全球化贸易的推动,在19世纪催生了统一时间标准的需求,时区由此诞生。
时区是地球上的区域使用同一个时间定义。理论时区采用其中央经线(或标准经线),以被15整除的经线为中心,向东西两侧延伸7.5度,即每15°划分一个时区。所以每差一个时区,区时相差一个小时。
具体就不展开说了,百科内容随处可见,这里贴个图吧:
说完时区,还要提到其他几个概念:
GMT - 格林尼治平均时间(Greenwich Mean Time,GMT)
是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。
自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。
格林尼治标准时间的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,已经被原子钟报时的协调世界时(UTC)所取代。
UTC - 协调世界时(Coordinated Universal Time)
是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林威治标准时间。
协调世界时是世界上调节时钟和时间的主要时间标准,它与0度经线的平太阳时相差不超过1秒,并不遵守夏令时。协调世界时是最接近格林威治标准时间(GMT)的几个替代时间系统之一。对于大多数用途来说,UTC时间被认为能与GMT时间互换,但GMT时间已不再被科学界所确定。
如今,格林威治时间仅仅是一个时区名字,主要被非洲和西欧的一些国家使用。
CST - 中国标准时间: China Standard Time (utc+8)
CST是众多地区时区简称中的一个,在国内特指中国标准时间,但是国外就不是了,还存在其他地区也在使用同样的时区简称,比如:
中原标准时间,Chungyuan Standard Time
澳洲中部时间,Central Standard Time (Australia)
北美中部时区,Central Standard Time (North America)
古巴标准时间,Cuba Standard Time
所以,为了避免歧义,现在多用CTT来指代中国时区。
除了CST、CTT,还存在多种地区时区简称,因为容易造成冲突引发歧义,时区简称方式正在逐步被替代,不推荐大家过多使用。
那在JAVA中有哪些时区相关的内容?
先来看一组代码:
LocalDateTime.now(); //2021-03-26T09:49:40.417744200
ZonedDateTime.now(); //2021-03-26T09:50:05.880025400+08:00[Asia/Shanghai]
Instant.now(); //2021-03-26T01:50:15.187036100Z
new Date(); //Fri Mar 26 09:50:25 CST 2021
观察代码可以看到,除了LocalDateTime.now()的toString()内容没有携带时区信息外,其他三种常见时间类的输出内容都带有时区,而LocalDateTime,顾名思义,其实只是使用了系统默认时区,所以这些常见的时间类的运作,都离不了时区的概念。
实际上,它们是通过以下几个类,来记录和展示时区内容的:
Java中一些时区相关Class
Class | From version |
---|---|
java.util.TimeZone; | 1.1 |
java.time.ZoneId; | 1.8 |
java.time.ZoneOffset extends ZoneId; | 1.8 |
由于TimeZone类的作用已经在1.8之后被新添加的时区类替代,这里就不讲了
ZoneId
这里偷懒,直接贴一下类注释:
A ZoneId is used to identify the rules used to convert between an Instant and a LocalDateTime. There are two distinct types of ID:
1. Fixed offsets - a fully resolved offset from UTC/Greenwich, that uses the same offset for all local date-times
2. Geographical regions - an area where a specific set of rules for finding the offset from UTC/Greenwich apply
里面提到说,ZoneId主要是用来做LocalDateTime和Instant的转换处理的,因为LocalDateTime使用了系统时区直接生成时间,对象信息内不包含时区信息,而Instant需要包含时区信息,所以在转换时需要指定时区。
而ZoneId的使用上有两种方式,第一种是固定偏移量,也就是跟UTC/GMT去比较,直接写+-多少小时多少分钟;第二种是使用特定的地区名,具体如下:
ZoneId.of(“xxx”);
- Z 或者+、-开头,如of(“Z”)、of(“+8”)、of(“-03:30”),即ZoneOffset格式
- 以”UTC”、”GMT”、”UT”开头,后跟+-,如of(“GMT+8”)
- 以洲名+地区名称组成,如Asia/Shanghai、Asia/Taipei
另外,如“CST”、“CTT”格式的时区简称方式不再被支持,因为部分简称存在冲突(如CST),并且无法对夏令时
提供支持,所以ZoneId类中还提供了对时区简称转地区名的方法:
ZoneId.SHORT_IDS.get("CTT"); //Asia/Shanghai
ZoneId.SHORT_IDS.get("CST"); //America/Chicago
所有旧时区简称列表如下:
EST - -05:00
HST - -10:00
MST - -07:00
ACT - Australia/Darwin
AET - Australia/Sydney
AGT - America/Argentina/Buenos_Aires
ART - Africa/Cairo
AST - America/Anchorage
BET - America/Sao_Paulo
BST - Asia/Dhaka
CAT - Africa/Harare
CNT - America/St_Johns
CST - America/Chicago
CTT - Asia/Shanghai
EAT - Africa/Addis_Ababa
ECT - Europe/Paris
IET - America/Indiana/Indianapolis
IST - Asia/Kolkata
JST - Asia/Tokyo
MIT - Pacific/Apia
NET - Asia/Yerevan
NST - Pacific/Auckland
PLT - Asia/Karachi
PNT - America/Phoenix
PRT - America/Puerto_Rico
PST - America/Los_Angeles
SST - Pacific/Guadalcanal
VST - Asia/Ho_Chi_Minh
而支持的地区名有几百种,此处就不一一列出了,可通过ZoneRulesProvider
类查看:
ZoneOffset
同样先摘录一下官方注释:
A time-zone offset from Greenwich/UTC, such as +02:00.
A time-zone offset is the amount of time that a time-zone differs from Greenwich/UTC. This is usually a fixed number of hours and minutes.
&emsp可以看到,它主要使用偏移量来配置时区,使用方式如下:
ZoneOffset.of(“xxx”);
Z - for UTC
+h
+hh
+hh:mm
-hh:mm
+hhmm
-hhmm
+hh:mm:ss
-hh:mm:ss
+hhmmss
-hhmmss
具体到代码里面的使用,举例如下:
Instant.now(); //2021-03-25T12:03:25.281066900Z
Instant.now().atZone(ZoneId.systemDefault()); //2021-03-25T20:05:19.977884400+08:00[Asia/Shanghai]
Instant.now().atZone(ZoneId.of("America/Chicago")); //2021-03-25T07:06:57.531644100-05:00[America/Chicago]
LocalDateTime.now().atZone(ZoneId.of("America/Chicago")); //2021-03-25T20:07:49.940338900-05:00[America/Chicago]
LocalDateTime.now().atZone(ZoneId.of("+8")); //2021-03-25T20:07:32.583907500+08:00
LocalDateTime.now().atZone(ZoneOffset.of("+8")); //2021-03-25T20:08:21.963101+08:00
看起来,使用offset方式明显更方便快捷一些,是不是只要记得当前时区对应的偏移量,需要换算时区的时候使用+-N带入时区信息计算就可以了呢?
答案是不是,因为有另外一个概念影响,叫夏令时:
夏令时( daylight saving time )
看命名可知,Save的是daylight时间,而夏天的daylight来的更早一些,所以要早起早睡,要在夏天到来时把时钟调快一些时间(一小时)。
施行的初衷是为了节约能源,合理利用日光,但会让报时工作变得更加复杂,并且会扰乱旅行、计费、纪录保存、医疗设备、重机设备...与睡眠模式的运作。
注:中国曾在1986-1991期间实施过夏令时。
因此可知,如果是中国这样的全部地区一个时区,并且没有夏令时的还好,不然的话,还要按照时间去切换+N-1,岂不烦死?
这时候就体现出用地区名方式时区的好了,此处直接上代码展示一下:
加拿大 . 纽芬兰,每年3月第2个周日开始夏令时并把时钟往前调1小时,每年11月第1个周日结束夏令时并把时钟往后调1小时。
ZoneId zoneId = ZoneId.of("Canada/Newfoundland");
LocalDateTime.of(2020,1,1,10,0,0).toInstant(ZoneOffset.of("+8")).atZone(zoneId);
//2019-12-31T22:30-03:30[Canada/Newfoundland]
LocalDateTime.of(2020,4,1,10,0,0).toInstant(ZoneOffset.of("+8")).atZone(zoneId);
//2020-03-31T23:30-02:30[Canada/Newfoundland]
可以看到,同样的时分秒,只因一个在夏天一个不是,换算成纽芬兰时区的时候就出现了差异,是不是很有趣呢?
Other Things:
我们使用jdbc连接mysql的时候,经常需要在url上配置serverTimezone=CTT,这是为什么呢?
这题我会!很明显这是配置时区的,使用CTT是为了CST有冲突,对吧?
没错!而且这个冲突其实发生的很搞笑,通过mysql查询show variables like '%time_zone%';
,一般能看到如下结果:
可以看到time_zone默认配置了系统时区,那么为什么服务器都部署在国内,且服务器时区都设置了+8,为什么还是有问题呢?
原因就在mysql的connector处理连接请求时,监测到time_zone为System,就会去取得system_time_zone中的CST,此时就有问题了,因为mysql中的CST代表中国标准时区,而java中却被识别为了美国中部时区,从+8变成了-6,一正一负就差了14小时啊!所以要么在mysql中指定具体时区为+8,要么就得在连接参数中指定了。
另外来看一看时间戳,在Java中常见的获取毫秒级时间戳的方式有:
1. System.currentTimeMillis()
2. Instant.now().getEpochSecond()
Returns: the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.
3. new Date().getTime()
Returns:
the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this date
虽然在注释中看到这几种都是拿当前时间与1970-1-1 00:00:00去比较,但是前两个比较的是UTC,后面一个比较的是GMT,不过其实都一样啦。