美文网首页
GMT?UTC?CST? 进来聊聊时区(in Java)

GMT?UTC?CST? 进来聊聊时区(in Java)

作者: 湘西刺客王胡子 | 来源:发表于2021-03-26 11:11 被阅读0次

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

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

    相关文章

      网友评论

          本文标题:GMT?UTC?CST? 进来聊聊时区(in Java)

          本文链接:https://www.haomeiwen.com/subject/sptjhltx.html