欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

格林尼治标准时间?UTC?CST?进来聊聊时区(Java 中的时区)

最编程 2024-05-08 11:31:26
...

先来看看几种常见的日期格式:

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

第一种肯定都不陌生,后面的也很常见,但是里面的TZCST+0800分别是什么意思呢?

T很简单,代表时间,国际标准规定的日期时间组合法,用T放在时间内容前,代表Time,而最后的部分,代表时区。

Timezone(时区)

什么是时区?

  世界各国位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。

  火车铁路与其他交通和通讯工具的发展,以及全球化贸易的推动,在19世纪催生了统一时间标准的需求,时区由此诞生。

  时区是地球上的区域使用同一个时间定义。理论时区采用其*经线(或标准经线),以被15整除的经线为中心,向东西两侧延伸7.5度,即每15°划分一个时区。所以每差一个时区,区时相差一个小时。
  具体就不展开说了,百科内容随处可见,这里贴个图吧:


image.png

  说完时区,还要提到其他几个概念:

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”);

  1. Z 或者+、-开头,如of(“Z”)、of(“+8”)、of(“-03:30”),即ZoneOffset格式
  2. 以”UTC”、”GMT”、”UT”开头,后跟+-,如of(“GMT+8”)
  3. 以洲名+地区名称组成,如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类查看:

image.png

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%';,一般能看到如下结果:

image.png

  可以看到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,不过其实都一样啦。